Using ActiveX Controls in Managed Code

Team-Fly    

 
.NET and COM Interoperability Handbook, The
By Alan Gordon
Table of Contents
Chapter Seven.  Advanced .NET to COM Interop

Using ActiveX Controls in Managed Code

ActiveX controls have always been one of the most misunderstood COM-based technologies. Microsoft didn't help the situation by changing the definition of what an ActiveX control is several times. The current definition of an ActiveX control is that it is a COM object that supports the IUnknown interface. ActiveX controls usually support many other interfaces, but all other interfaces should be viewed as optional and a container should not rely on these interfaces being supported.

However, to most people, an ActiveX control is a COM object that has a user interface and supports a special set of interfacesIOleControl, IOleIn-PlaceObject, IDataObject, and so forththat allow it to be hosted inside of (and displayed in-place within) an ActiveX control container. For the purposes of this discussion, I am going to use this second definition.

It's really not that difficult to understand how to use ActiveX controls from managed code if you understand how to use COM Interop in general. The coclasses , interfaces, and connection point events that ActiveX controls support are mapped to .NET classes, interfaces, and delegates in the same way as I discussed earlier in this chapter. The main difference is the way that you import an ActiveX control into your .NET projects. These differences are due to the fact that the ActiveX control has a user interface, so you must have a Windows Form wrapper for the control. You also need a way to add the ActiveX control to your toolbox in Visual Studio .NET.

You typically use an ActiveX control by dragging and dropping an icon that represents the control on to a form. You then set properties on the control that specify how the control will look and behave. Most ActiveX controls expose custom property pages that allow you to edit these properties in a convenient way. Most IDEs also support property browsers that you can use to edit the properties. Most ActiveX controls support source interfaces (that is, connection point events), and most ActiveX control-aware IDEs support a simple way to add handlers for events. Usually, you can add a handler by double-clicking a control and selecting the desired event from a list.

The .NET Framework supports Windows Forms controls, which are the managed code equivalent of ActiveX controls. Just like an ActiveX control, Windows Forms controls are displayed in place within a container, they have property pages, and they support events. You can probably guess then that the basic idea behind COM Interop for ActiveX controls is to make an ActiveX control appear as a Windows Forms control to a managed client. The .NET Framework SDK includes a tool called the Windows Forms ActiveX Control Importer ( aximp.exe ) that you can use to map an ActiveX control to a Windows Forms control. In addition to performing the same mapping that tlbimp does for the COM classes, interfaces, and source and event interfaces in the ActiveX control, this tool also generates a Windows Forms control that hosts the ActiveX control and exposes the ActiveX control's properties, methods , and events to managed code. The Windows Forms control that the ActiveX Control Importer generates derives from System.Windows.Forms.AxHost . This class, in turn , derives from System.Windows.Forms.Control .

You run the Windows Forms ActiveX Control Importer by entering the following command at a Visual Studio .NET command prompt:

 aximp [options] {filename.ocx  filename.dll} 

For example, the following command line will generate an Interop assembly and a Windows Forms control for an ActiveX control:

 aximp hw2cntrl.ocx 

This ActiveX control behaves like a rotary knob like the type you might see as the volume control on a stereo. Figure 7-11 shows what this control will look like in a client application. You can download the control from the Web site for this book, and I also included a document on the Web site that shows you how to build the control.

Figure 7-11. The rotary knob control displayed within a client application.

graphics/07fig11.jpg

When you run aximp.exe on an ActiveX control, the ActiveX Control Importer will generate two assemblies:

  • An Interop assembly with the name [TypeLibName].ocx that contains a very similar mapping of COM types to managed types as tlbimp.

  • A second assembly that contains a Windows Forms control with the name ax[TypeLibName].ocx .

where [TypeLibName] is the name of the type library for the ActiveX control. In the case of the rotary knob control, the two assemblies will have the names Hw2CntrlLib.dll and AxHw2CntrlLib.dll . The ActiveX control that you run this command on can have either the .ocx (which is more typical) or .dll extension.

Table 7-12 lists the command-line options for the ActiveX Control Importer.

Table 7-12. Available command-line parameters for the ActiveX Control Importer

Parameter

Description

out

Specifies the file name of the assembly that aximp will generate.

publickey

Allows you to specify the file that contains the public key that should be used to delay-sign the Interop assembly.

keyfile

Allows you to specify the file that contains the key pair that should be used to sign the Interop assembly.

delaysign

Used to specify that the Interop assembly will be delay signed.

source

Generate C# code for the Windows Forms wrapper.

nologo

Prevents tlbimp from displaying the Microsoft logo.

silent

Suppresses all output except errors.

verbose

Display extra diagnostic output information.

You can also use the ActiveX Control Importer from within Visual Studio. To do this, perform the following steps:

  1. Start Visual Studio .NET and create a C# Windows Forms project.

  2. Move your mouse over the Toolbox in Visual Studio .NET.

  3. The toolbox will slide right as shown in Figure 7-12.

    Figure 7-12. The Visual Studio .NET toolbox.

    graphics/07fig12.jpg

  4. Right-click the toolbox and select Customize Toolbox from the context menu. The Customize Toolbox dialog will appear as shown in Figure 7-13.

    Figure 7-13. The Customize Toolbox dialog.

    graphics/07fig13.jpg

  5. Click the COM Components tab.

  6. Select the desired ActiveX control from the list of components by clicking the checkbox next to the component.

  7. Click OK.

Visual Studio .NET will add an icon to your toolbox that represents the control. You can now drag and drop the icon onto a form to add an instance of the control to the form. After you add the control to a form, you can edit its properties using the property browser in Visual Studio .NET or the built-in custom property pages of the control. You can also add event handlers with a few mouse clicks.

Much like using COM Interop in general, using an ActiveX control from a managed code application is actually very simple to do. Understanding everything that's going on under the hood to make everything so simple is difficult to understand, however. Therefore, just as I did for generalized COM Interop, I will do a simple example that shows you how to use an ActiveX control from a managed code application. Then I'll go back and peer under the hood to see how it works.

For this example, I will use the rotary knob ActiveX Control that I used as a homework assignment for my COM class at UCLA Extension. The IDL for the rotary knob control is the following:

 [   uuid(D5ABDEA9-7D0A-11D0-818D-444553540000),   version(1.0) ] library HW2CNTRLLib {       importlib("stdole2.tlb");     // Forward declare all types defined in this typelib     dispinterface _DHw2cntrl;     dispinterface _DHw2cntrlEvents;     [       uuid(D5ABDEAA-7D0A-11D0-818D-444553540000),       hidden     ]     dispinterface _DHw2cntrl {         properties:           [id(0xfffffe0b), bindable, requestedit]             OLE_COLOR BackColor;           [id(0xfffffdff), bindable, requestedit]             OLE_COLOR ForeColor;           [id(0x00000001)]             short Range;           [id(0x00000002)]             VARIANT_BOOL AmbientBackGround;         methods:             [id(0xfffffdd8)]             void AboutBox();     };     [       uuid(D5ABDEAB-7D0A-11D0-818D-444553540000)     ]     dispinterface _DHw2cntrlEvents {         properties:         methods:             [id(0x00000001)]             void SettingChanged(short value);     };     [       uuid(D5ABDEAC-7D0A-11D0-818D-444553540000),       control     ]     coclass Hw2cntrl {         [default] dispinterface _DHw2cntrl;         [default, source] dispinterface _DHw2cntrlEvents;     }; }; 

This control has a default interface called _DHw2cntrl that contains BackColor, ForeColor, Range, and AmbientBackGround properties. BackColor and ForeColor are stock properties that affect the color of the rectangular area behind the control and the face of the knob respectively. Range is a custom property that controls the range of values that the knob sends to its client in the SettingChanged event as it moves through 360 degrees of rotation. Therefore, if you were using the knob to control an 8 bit color value, the Range property should be set to 255, because the color can take on any whole value between 0 and 255. The AmbientBackGround custom property allows you to specify that the control should always assume the background color of its container. The rotary knob control also has a default source interface called _DHw2cntrlEvents that contains an event called SettingChanged that the knob will fire as you turn it.

I will build the application shown in Figure 7-14, which uses three instances of the knob to control the RGB values of a color.

Figure 7-14. A client that uses the knob control.

graphics/07fig14.jpg

You can download the control from the Web site for this book. Make sure that the control is registered using regsvr32 before you start this exercise. Start by performing the following steps:

  1. Start Visual Studio .NET and create a C# Windows Forms project.

  2. Move your mouse over the Toolbox in Visual Studio .NET.

  3. The toolbox will slide right.

  4. Right-click the toolbox and select Customize Toolbox from the context menu. The Customize Toolbox dialog will appear.

  5. Click the COM Components tab.

  6. Select the item labeled Hw2cntrl Control.

  7. Click OK.

Tip

If you do not see the Hw2cntrl on the COM Components tab, you have not registered the ActiveX control. Use regsvr32 to register the control and then try again.


Visual Studio .NET will add an icon representing the control to the toolbox. The icon will look similar to the control itself. You can now drag and drop an instance of the control on to any of the forms in your project. When you do this, Visual Studio .NET will add a private member variable for the control to your form class, and it will add all the code needed to initialize the control (you will see this code shortly).

If you click on the ActiveX control that you dragged and dropped on to your form, you can see the properties for the control in the Visual Studio .NET property browser (see Figure 7-15).

Figure 7-15. The property browser.

graphics/07fig15.jpg

Notice that the AmbientBackGround and the Range properties display in this browser even though they are custom properties for this control. You can also edit the properties of the control by right-clicking on the control and selecting ActiveXProperties from the context menu. This will display the custom property pages for the control as shown in Figure 7-16.

Figure 7-16. The custom property pages for the knob control.

graphics/07fig16.jpg

For this example, add three instances of the knob control to the form. Call one axRed, one axGreen, and the other axBlue. Set the Range property on each of the controls to 255. Also add three text boxes to the form and give them the names txtRed, txtGreen, and txtBlue. Finally, add a Panel to the form. Your UI should look like Figure 7-14.

To add event handlers for the control, you can either double-click the control in Visual Studio .NET, which will add a handler for the default event of the control, which is the SettingChanged event. You can also click the Event tab on the property browser, which looks like a bolt of lightning (see Figure 7-17).

Figure 7-17. The event browser.

graphics/07fig17.jpg

You can add a handler for an event by double-clicking the left column of the event browser. Doing this will insert the code in your form needed to catch the event (I will look at this code shortly). Add event handlers for all three knobs . Notice that the custom SettingChanged event appears on the browser as well as a number of other standard events like Enter, Leave, and Resize. These other events are provided for you by the ActiveX infrastructure.

To implement the UI for this example, I'll first add the following three private variables to the form class:

 public class Form1 : System.Windows.Forms.Form { //code omitted...       private short mRedValue=0;       private short mGreenValue=0;       private short mBlueValue=0; // code omitted... } 

I'll then add the following code for the SettingChanged event handler on the red knob control:

 private void axRedKnob_SettingChanged(object sender,       AxHW2CNTRLLib._DHw2cntrlEvents_SettingChangedEvent e) {       txtRed.Text=e.value.ToString();       mRedValue=e.value;       SetPanelColor(); } 

All of the parameters for an event will be exposed as public properties on the second parameter to the event. The Windows Forms ActiveX Control Importer will create a class that contains properties for each parameter of an event. In this case, the class is called _DHw2cntrlEvents_ SettingChangedEvent , and it has a property called value, which is the argument for the SettingChanged method. I convert this value to a string and then store its textual representation in the txtRed edit box. I then store the new red value in the mRedValue member variable and then call the SetPanelColor method to update the color of the panel on the main window. I will naturally repeat this implementation for both the blue and green knobs. Simply replace txtRed with txtGreen or txtBlue and replace mRedValue with mGreenValue or mBlueValue. The implementation of the SetPanelColor method follows :

 private void SetPanelColor() {        panel1.BackColor= Color.FromArgb(mRedValue,mGreenValue,mBlueValue); } 

The SetPanelColor method sets the color of a panel equal to the RGB color defined by the mRedValue, mGreenValue, and mBlueValue variables. Your code is now complete; you can run the application. It's a fun application to play with. Turn the knobs and watch how the color on the panel changes.

Now that you have seen how easy it is to use ActiveX controls from a managed code application, let's look behind the scenes to see how COM Interop with ActiveX controls works. When you add a knob control to a Windows Form, Visual Studio .NET will add code to the InitializeComponent method in your form. The form's constructor calls the InitializeComponent method to initialize the controls on the form. The following code listing highlights some of the code that Visual Studio .NET will add:

 1.  #region Windows Form Designer generated code 2.  /// <summary> 3.  /// Required method for Designer support-do not modify 4.  /// the contents of this method with the code editor. 5.  /// </summary> 6.  private void InitializeComponent() 7.  { 8.      System.Resources.ResourceManager resources = new 9.       System.Resources.ResourceManager(typeof(Form1)); 10.     this.axRedKnob=new AxHW2CNTRLLib.AxHw2cntrl(); 11.     this.axGreenKnob=new AxHW2CNTRLLib.AxHw2cntrl(); 12.     this.axBlueKnob=new AxHW2CNTRLLib.AxHw2cntrl(); 13. // 14. // Initialization for other controls omitted here... 15. // 16.      ((System.ComponentModel.ISupportInitialize) 17.        (this.axRedKnob)).BeginInit(); 18.      ((System.ComponentModel.ISupportInitialize) 19.        (this.axGreenKnob)).BeginInit(); 20.      ((System.ComponentModel.ISupportInitialize) 21.        (this.axBlueKnob)).BeginInit(); 22.      this.SuspendLayout(); 23.      // 24.      // axRedKnob (repeated for green and blue knob) 25.      // 26.      this.axRedKnob.Enabled = true; 27.      this.axRedKnob.Location = 28.          new System.Drawing.Point(40, 56); 29.      this.axRedKnob.Name = "axRedKnob"; 30.      this.axRedKnob.OcxState = 31.          ((System.Windows.Forms.AxHost.State) 32.          (resources.GetObject("axRedKnob.OcxState"))); 33.      this.axRedKnob.Size=new 34.          System.Drawing.Size(72, 50); 35.      this.axRedKnob.TabIndex = 0; 35.      this.axRedKnob.SettingChanged += new 36.      _DHw2cntrlEvents_SettingChangedEventHandler 37.           (this.axRedKnob_SettingChanged); 38. // 39. // End repeated section for green and blue knob 40. // 41.     ((System.ComponentModel.ISupportInitialize) 42.         (this.axRedKnob)).EndInit(); 43.     ((System.ComponentModel.ISupportInitialize) 44.         (this.axGreenKnob)).EndInit(); 45.     ((System.ComponentModel.ISupportInitialize) 46.         (this.axBlueKnob)).EndInit(); 47.     this.ResumeLayout(false); 48. } 49. #endregion 

Note

The InitializeComponent method contains code that is generated by Visual Studio .NET's form designer. I did not write this code, and you should not ever change it because the form designer will likely overwrite your changes the next time it saves the code.


On lines 8 and 9, the InitializeComponent method instantiates a ResourceManager object. The ResourceManager class provides access to the Resources (icons, bitmaps, and, most important for this example, the persisted state of the ActiveX controls) of an application. On lines 10 through 12, the generated code instantiates the three instances of the knob controlaxRedKnob, axGreenKnob, and axBlueKnobthat I dropped onto the form. I omitted the code that instantiates the other Windows Forms controls on the form (the edit boxes, panels, and so forth). Lines 16 through 21 call the BeginInit method on the ISupportInitialize interface for each control. The System.Windows.Forms.AxHost class (which is the base class for the AxHW2CNTRLLib. AxHw2cntrl class that the ActiveX Control Importer creates to represent my ActiveX control) implements the ISupportInitialize interface to allow a client to initialize the control from a persistently stored state. Among other things, the BeginInit method will call the CreateSink in the AxHW2CNTRLLib. AxHw2cntrl class (I'll explain this method more shortly). This will attach a connection point sink object that the AxHW2CNTRLLib.AxHw2cntrl class implements to the default source interface that the underlying ActiveX control implements. On line 22, the InitializeComponent method calls the SuspendLayout method on the form, which will suppress multiple layout events while the InitializeComponent method positions and sizes all the controls on the form. A call to ResumeLayout near the end of the InitializeComponent method causes all these layout changes to take effect at once. Lines 26 through 37 show the code that sets the properties on the red knob. The InitializeComponent method contains similar logic for the Green and Blue knob, but I omitted this code because it's almost identical. Notice on lines 30 through 32 that the code fetches the persistently stored state of the ActiveX controls from the ResourceManager. Most ActiveX controls store the state of their properties into a container provided by their host. In the case of a Windows form, that container is the resource area of the executable. The code to catch the SettingChanged event on the red knob is shown on lines 35 through 37. Here I instantiate an instance of the delegate that maps to the SettingChanged event (_DHw2cntrlEvents_Setting ChangedEventHandler) passing in the name of the method in the form that will catch the event (axRedKnob_SettingChanged in this case). Then I assign this delegate to the SettingChanged event field on the ActiveX control wrapper. There is nothing unusual or ActiveX control specific about this code; it's the same code that you would write to catch any event on a managed class. Lines 41 through 46 contain a call to the ISupportInitialize.EndInit method on each control, which just signals the control that its container has completed initialization of the control. Line 47 contains the aforementioned call to the ResumeLayout method.

Code Regions

Notice the #region statement on line 1 and the corresponding #endregion statement on line 49 in the previous code listing. Code regions allow you to collapse and hide blocks of code so that you see an outline view of your source document. When a section of code is collapsed , you see a "+" symbol with a label next to it identifying the block of code as shown in Figure 7-18.

Figure 7-18. Collapsed code in a region.

graphics/07fig18.jpg

You can click the "+" to expand the code as shown in Figure 7-19.

Figure 7-19. Collapsed code in a region.

graphics/07fig19.jpg

You start one of these collapsible regions of code by using the #region and then specifying the text that should appear next to the "+" symbol when the code is collapsed. You end the region with a #endregion statement as shown here:

 #region Windows Form Designer generated code // // code goes here... // #endregion 

You can control Visual Studio .NET's outlining behavior using Outlining submenu on the Edit top-level menu. Visual Studio .NET uses #regions on its generated code, but you can use it in your own code also.

Table 7-10 showed the command-line parameters that you can pass to aximp.exe. One of the most interesting parameters is the source parameter. You can use this parameter to generate C# source code for the Windows Forms control that wraps your ActiveX control. It will write the source code to a file called ax[TypeLibName].cs . Now that you understand the code that a managed client uses to interact with an ActiveX control, let's take a look at the wrapper class that the Windows Forms ActiveX Control Importer generates for an ActiveX control. For our example ActiveX control, the file name is axHw2cntrlLib.cs . Some of the code generated by specifying the source parameter on the knob control follows:

 1.  namespace AxHW2CNTRLLib { 2.      [AxHost.ClsidAttribute 3.  ("{d5abdeac-7d0a-11d0-818d-444553540000}")] 4.      [DesignTimeVisibleAttribute(true)] 5.      [DefaultEvent("SettingChanged")] 6.      public class AxHw2cntrl : AxHost { 7. 8.          private HW2CNTRLLib._DHw2cntrl ocx; 9. 10.         private AxHw2cntrlEventMulticaster 11.        eventMulticaster; 12. 13.         private AxHost.ConnectionPointCookie 14.      cookie; 15. 16.         public AxHw2cntrl() : 17.                 base 18.   ("d5abdeac-7d0a-11d0-818d-444553540000") 19.       { 20.             // ... 21.         } 22.         [DispIdAttribute(2)] 23.         public virtual bool AmbientBackGround 24.       { 25.             get { 26.                 if ((this.ocx == null)) { 27.                     throw new 28.       AxHost.InvalidActiveXStateException 29.     ("AmbientBackGround", 30.       AxHost.ActiveXInvokeKind.PropertyGet); 31.                } 32.                return this.ocx.AmbientBackGround; 33.             } 34.             set { 35.                 if ((this.ocx == null)) { 36.                     throw new 37.     AxHost.InvalidActiveXStateException 38.     ("AmbientBackGround", 39.       AxHost.ActiveXInvokeKind.PropertySet); 40.                } 41.                 this.ocx.AmbientBackGround = value; 42.             } 43.         } 44.         [DispIdAttribute(-513)] 45.         public override Color ForeColor 46.        { 47.             get { 48.                 if (((this.ocx != null) 49.                   && (this.PropsValid() == true))) { 50.                     return GetColorFromOleColor 51.        (((UInt32)(this.ocx.ForeColor))); 52.                 } 53.                 else { 54.                     return base.ForeColor; 55.                 } 56.             } 57.             set { 58.                 base.ForeColor = value; 59.                 if ((this.ocx != null)) { 60.                     this.ocx.ForeColor = 61.  ((UInt32)(GetOleColorFromColor(value))); 62.                 } 63.             } 64.         } 65. 66.         public event 67.      _DHw2cntrlEvents_SettingChangedEventHandler 68.      SettingChanged; 69. 70.          protected override void CreateSink() 71.          { 72.              try { 73.                  this.eventMulticaster=new 74.          AxHw2cntrlEventMulticaster(this); 75.                  this.cookie=new 76.        AxHost.ConnectionPointCookie(this.ocx, 77.          this.eventMulticaster, 78.          typeof(_DHw2cntrlEvents)); 79.              } 80.              catch (System.Exception) { 81.              } 82.          } 83.          protected override void DetachSink() 84.         { 85.              try { 86.                  this.cookie.Disconnect(); 87.              } 88.              catch (System.Exception) { 89.              } 90.          } 91. 92.         internal void RaiseOnSettingChanged(93.      object sender, 94.      _DHw2cntrlEvents_SettingChangedEvent e) 95.      { 96.             if ((this.SettingChanged != null)) { 97.                 this.SettingChanged(sender, e); 98.             } 99.         } 100.     } 101. 102.     public delegate void 103.     _DHw2cntrlEvents_SettingChangedEventHandler(104.       object sender, 105.       _DHw2cntrlEvents_SettingChangedEvent e); 106. 107.     public class _DHw2cntrlEvents_SettingChangedEvent { 108. 109.          public short value; 110. 111.          public _DHw2cntrlEvents_SettingChangedEvent(112.          short value) 113.         { 114.              this.value = value; 115.          } 116.     } 117. 118.      public class AxHw2cntrlEventMulticaster : 119.       _DHw2cntrlEvents 120.     { 121. 122.          private AxHw2cntrl parent; 123. 124.          public AxHw2cntrlEventMulticaster(125.        AxHw2cntrl parent) 126.         { 127.              this.parent = parent; 128.          } 129. 130.          public virtual void SettingChanged(short val) 131.         { 132.          _DHw2cntrlEvents_SettingChangedEvent 133.        settingchangedEvent=new 134.      _DHw2cntrlEvents_SettingChangedEvent(val); 135.              this.parent.RaiseOnSettingChanged 136.        (this.parent, settingchangedEvent); 137.          } 138.      } 139. } 

I cleaned up the code and omitted some parts that were nonessential. Line 1 declares the namespace, AxHW2CNTRLLib, for the control, which is just the type library name, HW2CNTRLLib, with Ax prepended to it. Lines 2 through 6 contain the declaration of the class that will wrap the ActiveX control. In this case, the class is called AxHw2cntrl , which is just the class name of the ActiveX control with Ax prepended to it. Notice that this class derives from a class called AxHost , which resides in the System.Windows.Forms namespace and inherits from System.Windows.Forms.Control. Notice the attribute declarations on lines 2 through 5. The first attribute, AxHost.ClsidAttribute, contains the CLSID of the underlying ActiveX control. The next attribute, DesignTimeVisibleAttribute, indicates that the control should be visible at design time within a visual development environment. The last attribute, DefaultEvent, indicates which of the events should be treated as the default by a visual development environment. If you've used VB, you know that, when you double-click a button control, for instance, it will automatically add an event handler for the clicked event on the button. It does this because the clicked event is the default event for a button control. Similarly, the SettingChanged event is the default (and only) event for this control. Lines 7 through 15 contain the definition of three private fields in the AxHw2cntrl class. The first field is declared as follows:

 private HW2CNTRLLib._DHw2cntrl ocx; 

It contains the definition of an Interface reference called ocx. This field is typed as the default dispinterface in the ActiveX control, which is called _DHw2cntrl . The second field is typed as follows:

 private AxHw2cntrlEventMulticaster eventMulticaster; 

The Multicaster class is a connection point sink that implements the default source interface provided by the control and forwards the event to a .NET delegate. The last field is typed as follows:

 private AxHost.ConnectionPointCookie cookie; 

This field contains the cookie that represents a client's connection to a source interface. This cookie is returned by the Advise method in IConnectionPoint.

Lines 16 through 21 contain the definition of the constructor for the wrapper class. Notice that the constructor initializes the base class, System.Windows.Forms.AxHost , with the CLSID of the underlying ActiveX control. Lines 22 through 43 contain the wrapper for a custom property in the control. In this case, it's the AmbientBackGround property that determines whether the control uses the ambient background color, that is, the background color of its container. The get method for the property throws an exception of type, AxHost.InvalidActiveXStateException, if the internal field that represents the ActiveX control is null. If not, it just returns the value of the AmbientBackGround property. The set method does exactly the same null value check on the internal ActiveX control and throws the same error if the field is not set and sets the AmbientBackGround property on the ActiveX control otherwise . Notice that the dispid for this method is declared as an attribute on line 22. Lines 45 through 64 contain the wrappers for a stock property, ForeColor. These properties are handled slightly different because the base class of the wrapper control, System.Windows.Forms.AxHost , contains an implementation of these stock properties so there is no need to throw an exception. If the ocx field is non-null, the ForeColor property will return the value of the ForeColor property in the underlying ActiveX control after converting it from the OLE representation of a color to the .NET representation (System.Drawing.Color) using the GetColorFromOleColor method in the System.Windows.Forms.AxHost class. If the ocx field is null, the wrapper returns the ForeColor property from the base class, System.Windows.Forms.AxHost . You can find the declaration of a .NET event that is the managed code equivalent of the SettingChanged event in the ActiveX control on lines 66 through 68. On lines 70 through 82, you can see the definition of the CreateSink method, which is called to connect the Multicaster sink class that I described earlier with a connection point in the underlying ActiveX control. Lines 83 through 90 contain the definition of the DetachSink method, which is used to disconnect the Multicaster from the connection point. Lines 92 through 99 contain the definition of a method called RaiseOnSettingChanged that is called internally to send the SettingChanged event to the client. Lines 102 through 105 contain the definition of the delegate for the SettingChanged event. Like most event delegates, this delegate takes two parameters: (1) a sender, which contains the object that generated the event and (2) an object that contains the parameters for the event. The second parameter is typed as a _DHw2cntrlEvents_SettingChangedEvent , which is a class that is declared on lines 107 through 116. Unlike most event argument (EventArgs) classes, this class does not derive from System.EventArgs . This class contains a single public field, which is called value and typed as a short. This property maps to the value parameter passed in the SettingChanged event. Lines 118 through 138 contain the definition of the Multicaster class, which as I mentioned earlier, is just a connection point sink that implements the default source interface on the control, _DHw2cntrlEvents. This class is the connection point sink that will be passed to the Advise method of the IConnectionPoint implementation associated with the default source interface. Notice that the constructor for the Multicaster class takes a single parameter, which is an instance of the AxHw2cntrl class, which is the overall class that wraps the ActiveX control. The constructor stores this parameter in its private parent field. After you pass a Multicaster instance to the ActiveX control, the control will call the SettingChanged method in the Multicaster class whenever the SettingChanged event occurs. The SettingChanged method on the Multicaster class is defined on lines 130 through 137. This method will instantiate an instance of the event argument class for the SettingChanged event_DHw2cntrlEvents_SettingChangedEvent (see lines 132 through 134)and then, on lines 135 and 136, it will call the RaiseOnSettingChanged method (see lines 92 through 99) on the wrapper class through the parent field. It passes the parent object as the first parameter; this is the sender. It then passes the argument object that was instantiated on lines 132 through 134 as the event arguments parameter.

You can alter the source code for the wrapper control and recompile it if you need to. One case where you may want to do this is if your ActiveX control has more than one source (event) interface. The wrapper class generated by aximp maps only the events in default source interface to managed code equivalents. This is not as big a shortcoming as you might think because most ActiveX controls that I have seen only have a single source interface. If you happen to have an ActiveX control that has several source interfaces, you can modify the source code generated by running aximp with the /source parameter to map events in the non-default, source interfaces. A corollary to all of this is that, if you are creating an ActiveX control and you know ahead of time that you may need to use it through COM Interop, make sure that you only have a single source interface.


Team-Fly    
Top
 


. Net and COM Interoperability Handbook
The .NET and COM Interoperability Handbook (Integrated .Net)
ISBN: 013046130X
EAN: 2147483647
Year: 2002
Pages: 119
Authors: Alan Gordon

flylib.com © 2008-2017.
If you may any questions please contact us: flylib@qtcs.net