Working with Delegates


Now that you know the basic infrastructure of delegates and how they relate to event handling, you are now going to create an application that uses delegates. This will give you a basic understanding on how the event attributes create the injected code for events using the delegate classes.

For this first lesson, you are going to create a Windows Form application that contains four buttons and a label control to view the results of calling delegates. Create a new project by clicking New, Project from the File menu. Select Visual C++ Project from the list of project types and select Managed C++ Application from the list of available templates. Give your project the name DelegateTest and click OK to create the project. Because this is a Windows Form application, you will need to create a Windows Form. If you created the SimpleWindowsForm project from Hour 7, "Working with Windows Forms," you can use that as a base. Listing 18.1 contains the base Windows Form code that creates a Windows Form with four buttons and one label that you can use to create the user interface.

Listing 18.1 DelegateTest Windows Form Code
  1: #include "stdafx.h"   2:   3: #using <mscorlib.dll>   4: #include <tchar.h>   5:   6: #using <System.DLL>   7: #using <System.Drawing.DLL>   8: #using <System.Windows.Forms.DLL>   9:  10: using namespace System;  11: using namespace System::Drawing;  12: using namespace System::Windows::Forms;  13:  14: __gc class SimpleWindowsForm : public Form  15: {  16: protected:  17:    void InitForm();  18:    void Invoke_Click(Object* pSender, EventArgs* pEventArgs);  19:    void Bind1_Click(Object* pSender, EventArgs* pEventArgs);  20:    void Bind2_Click(Object* pSender, EventArgs* pEventArgs);  21:    void BindBoth_Click(Object* pSender, EventArgs* pEventArgs);  22:  23:     Label*  m_pOutput_L;  24:     Button* m_pInvoke_B;  25:     Button* m_pBind1_B;  26:     Button* m_pBind2_B;  27:     Button* m_pBindBoth_B;  28:  29: public:  30:     SimpleWindowsForm();  31:     virtual ~SimpleWindowsForm() {}  32: };  33:  34: SimpleWindowsForm::SimpleWindowsForm()  35: {  36:     InitForm();  37: }  38:  39: void SimpleWindowsForm::InitForm()  40: {  41:     // Allocate controls  42:     m_pOutput_L     = new Label;  43:     m_pInvoke_B     = new Button;  44:     m_pBind1_B      = new Button;  45:     m_pBind2_B      = new Button;  46:     m_pBindBoth_B   = new Button;  47:  48:     // m_pOutput_L Initialization  49:     m_pOutput_L->Location = Point(8, 8);  50:     m_pOutput_L->TabIndex = 1;  51:     m_pOutput_L->TabStop = false;  52:     m_pOutput_L->Text = S"Click a bind button";  53:     m_pOutput_L->Size = System::Drawing::Size(336, 23);  54:  55:     // m_pInvoke_B Initialization  56:     m_pInvoke_B->Location = Point(272, 40);  57:     m_pInvoke_B->TabIndex = 2;  58:     m_pInvoke_B->TabStop = true;  59:     m_pInvoke_B->Text = "Invoke";  60:     m_pInvoke_B->add_Click( new EventHandler( this, Invoke_Click ) );  61:  62:     // add bind button 1  63:     m_pBind1_B->Location = Point(8, 40);  64:     m_pBind1_B->TabIndex = 2;  65:     m_pBind1_B->TabStop = true;  66:     m_pBind1_B->Text = "Bind1";  67:     m_pBind1_B->add_Click( new EventHandler( this, Bind1_Click ) );  68:  69:     // add bind button 2  70:     m_pBind2_B->Location = Point(96, 40);  71:     m_pBind2_B->TabIndex = 2;  72:     m_pBind2_B->TabStop = true;  73:     m_pBind2_B->Text = "Bind2";  74:     m_pBind2_B->add_Click( new EventHandler( this, Bind2_Click ) );  75:  76:     // add both bind button  77:     m_pBindBoth_B->Location = Point(184, 40);  78:     m_pBindBoth_B->TabIndex = 2;  79:     m_pBindBoth_B->TabStop = true;  80:     m_pBindBoth_B->Text = "BindBoth";  81:     m_pBindBoth_B->add_Click( new EventHandler( this, BindBoth_Click ) );  82:  83:     // Add controls to the Form  84:     Controls->Add( m_pOutput_L );  85:     Controls->Add( m_pInvoke_B );  86:     Controls->Add( m_pBind1_B );  87:     Controls->Add( m_pBind2_B );  88:     Controls->Add( m_pBindBoth_B );  89:  90:     // Initialize Form attributes  91:     Size = System::Drawing::Size(365, 105);  92:     Text = "Delegate Test";  93: }  94:  95: void SimpleWindowsForm::Invoke_Click(Object* pSender,  96:                                      EventArgs* pEventArgs)  97: {  98: }  99: 100: void SimpleWindowsForm::Bind1_Click(Object* pSender, 101:                                     EventArgs* pEventArgs) 102: { 103: } 104: 105: void SimpleWindowsForm::Bind2_Click(Object* pSender, 106:                                     EventArgs* pEventArgs) 107: { 108: } 109: 110: void SimpleWindowsForm::BindBoth_Click(Object* pSender, 111:                                        EventArgs* pEventArgs) 112: { 113: } 114: 115: // This is the entry point for this application 116: int _tmain(void) 117: { 118:     Application::Run( new SimpleWindowsForm() ); 119:     return 0; 120: } 

As mentioned, this Windows Form contains four buttons. Two of the buttons, Bind1 and Bind2, will be used to select which delegate will be called. The third button, BindBoth, is used to support what is known as multicasting, which is the method an event source uses when it needs to notify multiple event receivers of an event. This will be explained a little later in the hour. The fourth button does the actual function call on the delegate. The last control is a label that will help you visualize how the delegates are working. Compile your application to make sure everything is working correctly. You should see a window similar to the one in Figure 18.2 when you run the application.

Figure 18.2. A Windows Form showing the layout of the buttons and label control for the DelegateTest application.


The general procedure you will implement consists of a single delegate and two functions. When you click one of the Bind buttons, it will bind the delegate to one of those two functions. Once that delegate is bound a function, any time the delegate method is called, it is routed to the bound function.

In your class declaration, create the declarations for the two functions you will bind. The return type is void, and they don't accept any parameters. You can call them BoundFunction1 and BoundFunction2. The next step is to create the declaration for the delegate you will use. This is simply a function with the same signature of the methods you created earlier, with the only difference being the __delegate attribute preceding the return type. The last data member you need to add is a pointer to the delegate function you just declared. Listing 18.2 shows the final class declaration. Because the functions and the member variables will only be used within the class and not by any external objects, you can place all the declarations within the private section of your class declaration.

Listing 18.2 Final Delegate Class Declaration
 1: __gc class SimpleWindowsForm : public Form  2: {  3: protected:  4:     void InitForm();  5:     void Invoke_Click(Object* pSender, EventArgs* pEventArgs);  6:     void Bind1_Click(Object* pSender, EventArgs* pEventArgs);  7:     void Bind2_Click(Object* pSender, EventArgs* pEventArgs);  8:     void BindBoth_Click(Object* pSender, EventArgs* pEventArgs);  9: 10:     Label*  m_pOutput_L; 11:     Button* m_pInvoke_B; 12:     Button* m_pBind1_B; 13:     Button* m_pBind2_B; 14:     Button* m_pBindBoth_B; 15: 16: public: 17:     SimpleWindowsForm(); 18:     virtual ~SimpleWindowsForm() {} 19: 20: private: 21:     void BoundFunction1(); 22:     void BoundFunction2(); 23: 24:     delegate void BoundFunction(); 25:     BoundFunction* m_pBoundFunction; 26: }; 

Before you can compile your application to make sure everything is working correctly, you'll need to add the two function definitions for BoundFunction1 and BoundFunction2. For now, just create an empty body for the functions. Now you're going to take a look at what happens just by adding that single __delegate attribute. In Class View, right-click your project and select Properties from the context menu. In the list displayed on the left side of the dialog, expand the C/C++ heading and select Output Files. On the right side, drop down the combo box labeled Expand Attributed Source and select the Yes option. Figure 18.3 shows the project properties dialog with the correct setting for the Expand Attributes Source property.

Figure 18.3. Changing project properties to expand the attributed source.


With the Expand Attributed Source property enabled, the compiler will create an external source file with the merged contents of your source and the injected code placed by the compiler. Build your application and then open the file DelegateTest.mrg.cpp. Look for the line in the file that shows the delegate declaration you created earlier. If you look at the code immediately following that, you'll see some interesting things. As mentioned earlier, even though a delegate behaves like a function pointer, it is actually a class. In this case, the class is named BoundFunction, which is also the name of the delegate function you created. Delegates are derived from the System::MulticastDelegate class within the .NET Framework. This class defines four functions. The first is the overloaded constructor. This constructor accepts a pointer to System::Object and IntPtr. Even though the type IntPtr sounds like a pointer to an integer (which wouldn't be a bad guess), it is actually a pointer value type whose size is the size of an integer on the machine it is running on. In other words, IntPtr is similar to a typeless pointer such as void* and can point to any memory address limited to the machine size of an integer. For the delegate constructor, IntPtr will be a pointer to the delegate function.

The next function within the MulticastDelegate class is the Invoke function. Rather than the delegate being called by name, it is called using this Invoke function. The Invoke method performs a synchronous call on the delegate method. In other words, once you call Invoke, the Invoke function will not return until the delegate method has returned. Following this function are two functions: BeginInvoke and EndInvoke. These two functions are designed to support asynchronous function calls. When BeginInvoke is called, the common language runtime (CLR) will queue the function call using a thread from the available threads within the thread pool and will immediately return before the function is called. This can be advantageous if the delegate function performs long calculations, and it frees you from having to worry about any threading issues. For this lesson, however, we are just going to focus on the synchronous Invoke method.

The next step is to add the event-handling code for the various "button click" events generated. When you click one of the bind functions, the delegate member variable is created and bound to one of the two bind functions, BoundFunction1 or BoundFunction2. As mentioned, the __delegate attribute creates a delegate class for you. The name of that delegate class is the same as the private delegate member variable you created. Even though that member variable appears like a function pointer, it is in fact a pointer to the class created when the __delegate attribute is expanded.

The delegate class constructor accepts two parameters. If you want the delegate to point to a static member function within your class, the first parameter to the class constructor is a null value, whereas the second parameter is a pointer to the static member function. The format to specify a static member function within a class for this parameter is Class::Function, and because it needs to be a pointer, you must precede it with the C++ address-of operator the ampersand. For this lesson, we will be using an instance member function, which means the constructor parameters are different compared to the previous method. The first parameter is a pointer to the instance class that contains the function you are going to bind. You need to pass it the current instance of the class, which in C++ is always maintained in C++ with the this pointer. The second parameter is a pointer to the function you are going to bind. The following is the code for the event handlers for the first two buttons in your application:

 1: void SimpleWindowsForm::Bind1_Click(Object* pSender, EventArgs* pEventArgs) 2: { 3:     m_pBoundFunction = new BoundFunction( this, BoundFunction1 ); 4: } 5: 6: void SimpleWindowsForm::Bind2_Click(Object* pSender, EventArgs* pEventArgs) 7: { 8:     m_pBoundFunction = new BoundFunction( this, BoundFunction2 ); 9: } 

If you have a background in Visual C++ 6, you'll notice that the naming scheme for events has changed. In technologies such as Microsoft Foundation Classes (MFC), events were prefaced with the word On followed by the event name, such as OnClick. Within the .NET Framework, however, the naming scheme was designed to easily associate an event with the object that fired it. This is accomplished by prefacing the event name with the object that fired that event. For instance, if a button named Button1 contains a Click event, the event handler would be named Button1_Click.

The third button, labeled BindBoth, is going to be used to demonstrate the multicasting capability of delegates. Multicasting is the ability to bind several functions to a single delegate. Every time another function is bound to a delegate, the function pointer is placed into a linked list. Once the delegate is invoked, each function is called in the order in which it was bound to the delegate. For this example, you are going to bind both of the bind functions to the delegate. The first step is to create two delegate pointers and create an instance of the delegate class for each of the bound functions. The Delegate class within the .NET Framework contains a function named Combine that combines a delegate with another. Therefore, call the Combine function using the two delegate variables, as shown in this code snippet:

 1: void SimpleWindowsForm::BindBoth_Click(Object* pSender, 2:                                        EventArgs* pEventArgs) 3: { 4:    BoundFunction* pBound1 = new BoundFunction( this, BoundFunction1 ); 5:    BoundFunction* pBound2 = new BoundFunction( this, BoundFunction2 ); 6: 7:    m_pBoundFunction = dynamic_cast<BoundFunction*> 8:        (Delegate::Combine(pBound1,pBound2)); 9: } 

Now that the delegate has the ability to bind to one or both of the member functions, you must now implement the code to invoke the delegate. In the event handler for the Invoke button, first clear the label control by setting its Text property to an empty string. Finally, call the Invoke function of the delegate variable. Because a user may inadvertently click Invoke before binding any of the functions to the delegate, you should first check to make sure the delegate has been created. If it has been created, then the pointer is valid and not null. The event handler for the Invoke button is shown here:

 1: void SimpleWindowsForm::Invoke_Click(Object* pSender, EventArgs* pEventArgs) 2: { 3:     // clear label text 4:     m_pOutput_L->Text = ""; 5: 6:     if( m_pBoundFunction ) 7:         m_pBoundFunction->Invoke(); 8: } 

The final step for your application is to implement the functions called by the delegate class. The functions will simply set the label's Text property, signifying which function is being called. However, in the case of a multicast delegate, if the second function in the list is called and overwrites the label's text, you have no way of knowing whether the first function was ever called. Fortunately, there is a solution. The MulticastDelegate class, which your delegate class is derived from, contains a member function called GetInvocationList. This function returns an array of delegates for each function bound to the main delegate. Because this is an array, you can query the Count property to get the number of functions the main delegate is bound to. Therefore, if there is more than one function bound to the delegate, you should concatenate the string you wish to output to the current string displayed in the label control. The bound function implementations are shown here:

  1: void SimpleWindowsForm::BoundFunction1()  2: {  3:     if( m_pBoundFunction->GetInvocationList()->Count > 1 )  4:         m_pOutput_L->Text = String::Concat(m_pOutput_L->Text,  5:                                            S" BoundFunction1 Called! ");  6:     else  7:         m_pOutput_L->Text = "BoundFunction1 Called!";  8: }  9: 10: void SimpleWindowsForm::BoundFunction2() 11: { 12:     if( m_pBoundFunction->GetInvocationList()->Count > 1 ) 13:         m_pOutput_L->Text = String::Concat(m_pOutput_L->Text, 14:                                            S" BoundFunction2 Called! "); 15:     else 16:         m_pOutput_L->Text = "BoundFunction2 Called!"; 17: } 

You are now finished with the DelegateTest application. After you compile and run your application, bind the delegate to the member functions by clicking one of the bind buttons. You can then click the Invoke button to call the delegate's Invoke method. If you click the BindBoth button and then click Invoke, your application should appear as shown in Figure 18.4.

Figure 18.4. Output from utilizing a multicast delegate.



Sams Teach Yourself Visual C++. NET in 24 Hours
Sams Teach Yourself Visual C++.NET in 24 Hours
ISBN: 0672323230
EAN: 2147483647
Year: 2002
Pages: 237

Similar book on Amazon © 2008-2017.
If you may any questions please contact us: