Home

Delegates and Events

 

Delegates

 

Introduction

As a variable can be declared as a pointer so can a function. As we saw in the previous sections, a pointer to a function is a type of variable whose name can be used as a variable although it is not a traditional variable like the others. This concept has always been very helpful in Microsoft Windows programming because it allows the use of callback functions. Thanks to their effectiveness (and legacy code), the concept of callback functions was carried out in the .NET Framework but they were defined with the name of delegate.

A delegate is a special type of user-defined variable that references a method of a class. There are similarities and differences between a function pointer and a delegate:

  • Like a function pointer, a delegate is not defined like a normal function
  • Like a function pointer, a delegate has the appearance of a variable that is declared as a pointer
  • Like a function pointer, a delegate can take argument(s)
  • While a function pointer can be associated with a global function, that is, a function that is not a member of a class, a delegate can be associated with, or can reference, only a member of a class

Delegate Declaration 

To declare a delegate, you use the delegate keyword. The basic formula used to create a delegate is:

Access Level delegate Function-Signature;

The Function-Signature factor is created as you would declare a normal C++ function. This means that the Function-Signature must have

  1. A return type: The return type can be void, one of the primitive data types (int, long, double, short, etc), a .NET Framework type (Double, Decimal, etc), a .NET Framework class, or a class you created
  2. A name: Like every variable, function, or class, a delegate must have a name
  3. Parentheses: Like every function, a delegate must have parentheses
  4. Optional arguments: If the method(s) that this delegate will reference has/have argument(s), this delegate must also have argument(s) of exactly the same kind

After defining the Function-Signature, you must end the delegate declaration with a semi-colon. Here is an example:

delegate double Addition();

A declaration of a delegate can also use an access level as public or private. If you plan to use the delegate only locally, you can omit the access level or declare it as private, which is the default. If you plan to use the delegate outside of the project in which it is created, you should declare it as public.

After declaring a delegate, remember that it only provides a skeleton for a method, not an actual method. In order to use it, you must define a method that would carry an assignment the method is supposed to perform. That method must have the same return type and the same (number of) argument(s), if any. Here is an example:

private delegate double Addition();

public ref class CMathOperations
{
public:
    double Plus()
    {
	double a = 248.66, b = 50.28;

	return a + b;
    }
};

After implementing the method, you can associate it to the name of the delegate. To do that, where you want to use the method, first declare a pointer of the type of the delegate using the gcnew operator and calling its default constructor.

Techniques of Using a Delegate

After properly declaring and initializing the instance, it becomes ready and you can use it. When you declare a delegate, at run time, the compiler generates a class for it. The generated class automatically receives the following characteristics:

  • The class is derived from the System::MulticastDelegate class which itself derives from the System::Delegate class. Both classes are defined in the System.dll assembly
  • The class is equipped with a default constructor and two other constructors that takes two arguments as follows:
      
    protected:
        MulticastDelegate();
    
    protected:
        MulticastDelegate(Object^ target, String^ method);
    
    protected:
        MulticastDelegate(Type^ target, String^ method);
  • The class is automatically equipped with a method called Invoke. Its return type and its argument become the same as those of the declared delegate

As mentioned in the second point, the instance of the delegate can take two arguments. If you declared the method as done earlier (delegate double Addition();), the first argument of the instance must be a pointer to the class that implements the method associated with the delegate. This would be done as follows:

using namespace System;

private delegate double Addition();

public ref class CMathOperations
{
public:
    double Plus()
    {
	double a = 248.66, b = 50.28;

	return a + b;
    }
};

int main()
{
    CMathOperations ^ oper = gcnew CMathOperations;
    Addition ^ add = gcnew Addition(oper, . . .);
    
    return 0;
}

After the first argument, the second argument must be a reference to the class' method that implements the desired behavior of the delegate. Here is an example:

using namespace System;

private delegate double Addition();

public ref class CMathOperations
{
public:
    double Plus()
    {
	double a = 248.66, b = 50.28;

	return a + b;
    }
};

int main()
{
    CMathOperations ^ oper = gcnew CMathOperations;
    Addition ^ add = gcnew Addition(oper, &CMathOperations::Plus);
    
    return 0;
}

The declaration gives meaning to the delegate. To actually use the method, you can call the name of the delegate as if it were a defined method. If you want to retrieve the value that the delegate produces, in the above third point, we mentioned that the delegate's class is equipped with a method called Invoke that completely "understands" the method that implements it. This means that the Invoke() method returns the same value as the associated method. In our example, the Invoke() method of the delegate would return a double since the delegate was declared as returning a double.

Here is an example that uses the delegate:

using namespace System;

private delegate double Addition();

public ref class CMathOperations
{
public:
    double Plus()
    {
	double a = 248.66, b = 50.28;

	return a + b;
    }
};

int main()
{
    CMathOperations ^ oper = gcnew CMathOperations;
    Addition ^ add = gcnew Addition(oper, &CMathOperations::Plus);

    Console::WriteLine(L"Value = {0}", add->Invoke());
    return 0;
}

This would produce:

Value = 298.94
Press any key to continue . . .

We mentioned earlier that you could declare the associated method of a delegate like any normal method, in which case you must pass a pointer to the class as the first argument of the instance of the delegate. This is a requirement if you plan to instantiate the delegate outside of the class that defines its associated method. That's the case for our MathOperations class. If you plan to use a delegate in the same class that implements its associated method, you can declare the method static. Here is an example:

private delegate double Addition();
private delegate double Multiplication();

public ref class CMathOperations
{
public:
    double Plus()
    {
	double a = 248.66, b = 50.28;

	return a + b;
    }

    static double Times()
    {
	double a = 248.66, b = 50.28;

	return a * b;
    }
};

Once again, to use the delegate, you must first declare its pointer and allocate its memory using the gcnew operator and calling the constructor that takes two arguments. This time, the first argument must be null or 0. The second argument must still be a reference to the method that implements the desired behavior. Here is an example:

using namespace System;

private delegate double Addition();
private delegate double Multiplication();

public ref class CMathOperations
{
public:
    CMathOperations()
    {
        multi = gcnew Multiplication(&Times);
    }

    double Plus()
    {
	double a = 248.66, b = 50.28;

	return a + b;
    }

    static double Times()
    {
	double a = 248.66, b = 50.28;

	return a * b;
    }

    void ShowResult()
    {
	Console::WriteLine(L"Multiplication = {0}", multi->Invoke());
    }

private:
    Multiplication ^ multi;
};

int main()
{
    CMathOperations ^ oper = gcnew CMathOperations;
    Addition ^ add = gcnew Addition(oper, &CMathOperations::Plus);

    Console::WriteLine(L"Addition = {0}", add->Invoke());
    oper->ShowResult();

    return 0;
}

This would produce:

Addition = 298.94
Multiplication = 12502.6248
Press any key to continue . . .

Because delegates are usually declared globally, that is, outside of a class, they can be associated with a method of any class, provided the method has the same return type (and the same (number of) argument(s)) as the delegate.

Delegates Compositions

One of the characteristics that set delegates apart from C/C++ function pointers is that one delegate can be added to another using the overloaded addition assignment operator. This is referred to as composition.

A Delegate With One of More Arguments

If you want to associate a method that takes arguments to a delegate, when declaring the delegate, provide the necessary argument(s) in its parentheses. Here is an example of a delegate that takes two arguments (and returns a value):

delegate double Addition(double x, double y);

When defining the associated method, besides returning the same type of value if not void, make sure that the method takes the same number of arguments. Here is an example:

private delegate double Addition(double x, double y);
private delegate double Multiplication(double x, double y);

public ref class CMathOperations
{
public:
    double Plus(double x, double y)
    {
	return x + y;
    }
};

Once again, to associate the method, declare a variable of the type of delegate and pass a reference to the method as the second argument to the constructor of the delegate. Here is an example:

private delegate double Addition(double x, double y);
private delegate double Multiplication(double x, double y);

public ref class CMathOperations
{
public:
    CMathOperations()
    {
        add = gcnew Addition(&Plus);
        multi = gcnew Multiplication(&Times);
    }

    static double Plus(double x, double y)
    {
	return x + y;
    }

    static double Times(double x, double y)
    {
        return x * y;
    }

private:
    Addition ^add;
    Multiplication ^ multi;
};

Notice that only the name of the method is passed to the delegate. To actually use the value that the delegate produces, when calling its Invoke() method, you must pass the exact number of arguments with their correct values:

using namespace System;

private delegate double Addition(double x, double y);
private delegate double Multiplication(double x, double y);

public ref class CMathOperations
{
public:
    CMathOperations()
    {
        add = gcnew Addition(&Plus);
        multi = gcnew Multiplication(&Times);
    }

    static double Plus(double x, double y)
    {
	return x + y;
    }

    static double Times(double x, double y)
    {
        return x * y;
    }

    void ShowResult()
    {
	Console::WriteLine(L"Addition = {0}", add->Invoke(24.56, 98.76));
	Console::WriteLine(L"Multiplication = {0}", multi->Invoke(24.56, 98.76));
    }

private:
    Addition ^add;
    Multiplication ^ multi;
};

int main()
{
    CMathOperations ^ oper = gcnew CMathOperations;
   
    oper->ShowResult();

    return 0;
}

This would produce:

Addition = 123.32
Multiplication = 2425.5456
Press any key to continue . . .

A Delegate Passed as Argument

Using delegates, one method can be indirectly passed as argument to another method. To proceed, first declare the necessary delegate. Here is a example of such a delegate:

public delegate double Squared(double x);

A delegate can be passed as argument to a method. Such an argument would be used as if it were a method itself. This means that, when accessed in the body of the method, the name of the delegate must be accompanied by parentheses and if the delegate takes an argument or arguments, the argument(s) must be provided in the parentheses of the called delegate. Here is an example:

public delegate double Squared(double x);

public ref class CCircle
{
private:
    double _radius;

public:
    double Area(Squared ^ sqd)
    {
	return sqd(_radius) * Math::PI;
    }

public:
    property double Radius
    {
        double get() { return _radius; }
        void   set(double r) { _radius = r; }
    }
};

After declaring a delegate, remember to define a method that implements the needed behavior of that delegate. Here is an example:

public delegate double Squared(double x);

public ref class CCircle
{
private:
    double _radius;

public:
    double Area(Squared ^ sqd)
    {
	return sqd(_radius) * Math::PI;
    }

    static double ValueTimesValue(double Value)
    {
	return Value * Value;
    }

public:
    property double Radius
    {
        double get() { return _radius; }
        void   set(double r) { _radius = r; }
    }
};

You can also define the associated method in another class, not necessarily in the class where the delegate would be needed. Once the method that implements the delegate is known, you can use the delegate as you see fit. To do that, you can declare a pointer of the type of that delegate and pass the implementing method to its constructor. Here is an example:

public delegate double Squared(double x);

public ref class CCircle
{
private:
    double _radius;

public:
    double Area(Squared ^ sqd)
    {
	return sqd(_radius) * Math::PI;
    }

    static double ValueTimesValue(double Value)
    {
	return Value * Value;
    }

public:
    property double Radius
    {
        double get() { return _radius; }
        void   set(double r) { _radius = r; }
    }

    void CircleCharacteristics()
    {
	Squared ^ Sq = gcnew Squared(ValueTimesValue);
    }
};

This declaration gives life to the delegate and can then be used as we have proceed with delegates so far. Here is an example:

using namespace System;

public delegate double Squared(double x);

public ref class CCircle
{
private:
    double _radius;

public:
    
    double Area(Squared ^ sqd)
    {
	return sqd(_radius) * Math::PI;
    }

    static double ValueTimesValue(double Value)
    {
	return Value * Value;
    }

public:
    property double Radius
    {
        double get() { return _radius; }
        void   set(double r) { _radius = r; }
    }
};

int main()
{
    CCircle ^ round = gcnew CCircle;
    Squared ^ sqr   = gcnew Squared(CCircle::ValueTimesValue);

    round->Radius = 32.48;
    Console::WriteLine(L"Circle Characteristics");
    Console::WriteLine(L"Radius: {0}", round->Radius);
    Console::WriteLine(L"Area:   {0}", round->Area(sqr));

    return 0;
}

This would produce:

Circle Characteristics
Radius: 32.48
Area:   3314.22442654161
Press any key to continue . . .

Events

 

Introduction

A Windows control is an object that allows a person to interact with a computer. The object is made part if an application. A person can use to submit a number, to request a value, or to present a behavior. When a control A requests a value or service from another control B, control A is referred to as a client of control B. This relationship is important not simply because it ensures the flow of data between both controls but also because control B should be ready to provide the value or behavior that a client needs at a certain time.

While a control B is asked to provide some values to, or perform some assignment(s) for, another control A, many things could happen. In fact, there is an order that the actions should follow. For example, during the lifetime of a program, that is, while a program is running, a control may be holding a value it can provide to its client but at another time, that value may not be available anymore, for any reason; nothing strange, this is just the way it happens. Because different things can happen to a control B while a program is running, and because only control B would be aware of these, it must be able to signal to the other control when there is a change. This is the basis of events: An event is an action that occurs on an object and affects it in a way that its clients must be made aware of.

As mentioned already, an event is an action that occurs. For example, suppose you are spaghetti and the red sauce regularly draws some lines on your lips. You know this because your brain sends a signal that your mouth is dirty. To appear fine, you must wipe your mouth regularly. Every time you have wiped your mouth, another signal is sent to the brain to let it know that your mouth is fine now (and when the mouth becomes dirty again, another signal lets the brain know). These signals prompt you to do something. Signaling an event is also referred to as firing it.

Based on its concept, an event is carried by a delegate. This means that, in order to fire an event, you must have defined an appropriate delegate. For good habits, the name of a delegate that signals an event includes EventHandler. For example, if you are declaring a delegate that would carry the signal when your mouth has been wiped, you may call it WipeMouthEventHandler. The WipeMouth part indicates the possible type of signal. The EventHandler part specifies that this delegate is primarily used to handle an event.

A delegate may need additional information, as argument(s) in order to carry its signal. You can declare a delegate that doesn't take an argument. For example, when the mouth has been wiped, if you simply want to signal that it has been, you can fire a simple event accordingly. The delegate of such an event may not take any argument. It could be declared as follows:

delegate void WipeMouthEventHandler();

You can also have a delegate that takes one argument. For example, if you want to define a delegate that would carry the signal that your mouth has been wiped, the delegate can take as arguments the name of the hand that wiped the mouth (unless you can afford an assistant who would do that for you). Such a delegate could be declared as follows:

delegate void WipeMouthEventHandler(int LeftOrRightHand);

You can also have a delegate that takes more than one argument. For example, when you wipe your mouth, you may use the reverse of your hand, your shirt, or a napkin. To define your delegate, you can provide an additional argument that specifies what you would use to wipe your mouth.

As seen earlier, after declaring a delegate, you must define the method that implements it. Here is an example:

delegate void WipeMouthEventHandler(int LeftOrRightHand);

public ref class CExercise : public Form
{
public:
	CExercise(void)
	{
		InitializeComponent();
	}

private:
	void InitializeComponent()
	{
	}
	
	static void MouthWiper(int LeftOrRightHand)
	{
	}
};
 

Practical LearningPractical Learning: Introducing Events

  1. Star Microsoft Visual C++
  2. To create a new application, on the main menu, click File -> New -> Project...
  3. In the Project Types list, click Visual C++ and, in the Templates list, click Windows Forms Application
  4. In the Name box, replace the content with RectangleExercise2 and press Enter
  5. Design the form as follows:
     
    Control Text Name
    Label Length:  
    TextBox 0.00 txtLength
    Label Height:  
    TextBox 0.00 txtHeight
    Button Calculate btnCalculate
    Label Perimeter:  
    TextBox 0.00 txtPerimeter
    Label Area:  
    TextBox 0.00 txtArea
    Button Close btnClose
  6. To create a new class, on the main menu, click Project -> Add Class...
  7. In the Categories list, expand Visual C++ and click C++
  8. In the Templates list, make sure C++ Class is selected and click Add
  9. Set the Class Name to CRectangle and click Finish
  10. Change the Rectangle.h header file as follows:
     
    #pragma once
    
    public ref class CRectangle
    {
    private:
        double len;
        double hgt;
    
    public:
        CRectangle(void);
        CRectangle(double length, double height);
    
        property double Length
        {
    	double get() { return len; }
    	void set(double L) { len = L; }
        }
    
    	property double Height
        {
            double get() { return hgt; }
            void set(double H) { hgt = H; }
        }
    
        property double Perimeter
        {
            double get() { return (len + hgt) * 2; }
        }
    
        property double Area
        {
            double get() { return len * hgt; }
        }
    };
  11. Change the Rectangle.cpp source file as follows:
     
    #include "StdAfx.h"
    #include "Rectangle.h"
    
    CRectangle::CRectangle(void)
        : len(0.00), hgt(0.00)
    {
    }
    
    CRectangle::CRectangle(double length, double height)
        : len(length), hgt(height)
    {
    }
  12. Save all

Event Creation

An event is declared like a pseudo-variable but based on a delegate. To actually declare an event, you use the event keyword with the following formula:

Access Level event PointerToDelegate NameOfEvent;

The Access Level factor specifies whether the events will be used as private, public, or protected using the appropriate C++ keyword: public, private, or protected. You can omit the access level. If you do, the event is considered private.

The event keyword is required.

To define an event, you must specify the delegate that is used to carry the event. To do this, you must provide the delegate as a handle.

Like everything in a program, an event must have a name. This would allow the clients to know what (particular) event occurred. To be indicative, the name of an event starts with On and usually ends with the beginning name of its delegate without the EventHandler part. Here is an example:

delegate void WipeMouthEventHandler(int LeftOrRightHand);

public ref class CExercise : public Form
{
public:
	event WipeMouthEventHandler ^ OnWipeMouth;

	CExercise(void)
	{
	}
};

When the event occurs, its delegate would be invoked. This specification is also referred to as hooking up an event. As the event occurs (or fires), the method that implements the delegate runs. This provides the functionality of the event and makes the event ready to be used. Both the declarations of the delegate and of the event we have performed above are not related, we only gave them names that resemble each other. Before using an event, you must combine it to the delegate that will carry it. To do this, you must declare a handle to the event. To initialize it, you would call the constructor of the delegate, passing it the two arguments that we reviewed earlier. To combine the delegate and the event, you initialize the event using the += operator. Once this is done, you can call the event. Here is an example:

//#pragma once
#include <windows.h>

#using <System.dll>
#using <System.Windows.Forms.dll>

using namespace System;
using namespace System::Windows::Forms;

public delegate void WipeMouthEventHandler(int LeftOrRightHand);

public ref class CExercise : public Form
{
public:
	event WipeMouthEventHandler ^ OnWipeMouth;

	CExercise(void)
	{
		InitializeComponent();
	}

private:
	void InitializeComponent()
	{
		OnWipeMouth += gcnew WipeMouthEventHandler(&MouthWiper);
	}
	
	static void MouthWiper(int LeftOrRightHand)
	{
	}
};

int APIENTRY WinMain(HINSTANCE hInstance,
		     HINSTANCE hPrevInstance,
		     LPSTR lpCmdLine,
                     int nCmdShow)
{
	Application::Run(gcnew CExercise());

	return 0;
}

Implementation of Events

 

Introduction

An application is made of various objects or controls. During the lifetime of an application, its controls regularly send messages to the operating system to do something on their behalf. These messages are similar to human messages and must be processed appropriately. Since most of the time more than one application is running on the computer, the controls of such an application also send messages to the operating system. As the operating system is constantly asked to perform these assignments, because there can be so many requests presented unpredictably, the operating system leaves it up to the controls to specify what they want, when they want it, and what behavior or result they expect. These scenarios work by the controls sending events.

Events in the .NET Framework are implements through the delegates. The most common events have already been created for the objects of the .NET Framework controls so much that you will hardly need to define new events, at least not in the beginning of your GUI programming adventure. Most of what you will do consists of implementing the desired behavior when a particular event fires. To start, you should know what events are available, when they are needed, how they work, and what they produce.

To process a message, it (the message) must provide at least two pieces of information: What caused the message and what type of message is it? Both values are passed as the arguments to the event. Since all controls used in the .NET Framework are based on the Object class, the first argument must be an Object type and represents the control that sent the message. 

As mentioned already, each control sends its own messages when necessary. Some messages are unique to some controls according to their roles. Some other messages are common to various controls, as they tend to provide similar actions. To manage such various configurations, the .NET Framework considers the messages in two broad categories.

Some messages don't require much information. This type of message would be sent without much detailed information. This type of message is carried by an argument of type EventArgs passed as the second parameter of the event.

Some messages must be accompanied by additional information. When a message must carry additional information, the control that sent the message specifies that information by the name of the second argument. Because there are various types of messages like that, there are also different types of classes used to carry such messages. We will introduce each class when appropriate.

Event Implementation

Although there are different means of implementing an event, there are three main ways you can initiate its coding. If the control has a default event and if you double-click it, the studio would initiate the default event and open the Code Editor. The cursor would be positioned in the body of the event's code, ready to receive your instructions. Another technique you can use consists of displaying the form first and clicking either the form or the control that will fire the event. Then, in the Properties window, click the Events button Events, and double-click the name of the event you want to use.

You can also manually code an event. To do this, first define the method that will carry the event. Here is an example:

public ref class CExercise : public Form
{
public:
	CExercise(void)
	{
		InitializeComponent();
	}

private:
	void InitializeComponent()
	{
	}
	
	Void WasClicked(Object ^ sender, EventArgs ^ e)
	{
		Close();
	}
};

Then, add the control if it is not added yet. Use the += operator to assign the type of delegate that handles the event, passing as the second argument the name of the method defined above. This assignment must be performed on the name of the event that will be fired. Here is an example:

#pragma once
#include <windows.h>

#using <System.dll>
#using <System.Windows.Forms.dll>

using namespace System;
using namespace System::Windows::Forms;

public ref class CExercise : public Form
{
private:
    Button ^ btnClose;
public:
    CExercise(void)
    {
	InitializeComponent();
    }

private:
    void InitializeComponent()
    {
	btnClose = gcnew Button;
	btnClose->Text = L"Close";
	btnClose->Click += gcnew EventHandler(this, &CExercise::WasClicked);
	Controls->Add(btnClose);
    }
	
private:
    void WasClicked(Object ^ sender, EventArgs ^ e)
    {
	Close();
    }
};

int APIENTRY WinMain(HINSTANCE hInstance,
                     HINSTANCE hPrevInstance,
                     LPSTR    lpCmdLine,
                     int       nCmdShow)
{
    Application::Run(gcnew CExercise);
    return 0;
}

Practical LearningPractical Learning: Generating an Event

  1. Display the form and double-click the Close button
  2. Notice that the caret is positioned in the Code Editor inside the code of the event
  3. Implement the event as follows:
     
    System::Void btnClose_Click(System::Object^  sender, System::EventArgs^  e)
    {
        Close();
    }
  4. Execute the application to test it and click the Close button
 
 

Previous Copyright 2007, yevol.com Next