Home > Articles > Programming > Windows Programming

This chapter is from the book

Handling ActiveX Control Events in Managed Code

As discussed in the previous two chapters, the ActiveX importer produces its own classes that wrap coclasses representing ActiveX controls as Windows Forms controls. If an ActiveX Assembly didn't also contain some extra transformations for events, then the AxHost-derived wrapper classes that you can host in a Windows Forms control would not appear to have any events. Therefore, the ActiveX importer must clearly do some transformations as well. These transformations, and their use, are covered in this section.

ActiveX Importer Transformations

Just as classes generated by the type library importer contain event members when the coclass lists an interface marked with the IDL [source] attribute, classes generated by the ActiveX importer also contain event members. However, only event members corresponding to the default source interface are created. Any non-default source interfaces are ignored. Name conflicts are handled by appending an Event suffix to applicable event names.

Besides the additions made to the AxHost-derived classes (more of which are shown in the next listing), the ActiveX importer creates some additional types every time it encounters a coclass listing a source interface:

  • SourceInterfaceName_MethodNameEventHandler—A delegate, one for each method on the default source interface (excluding methods that have no parameters, which use the System.EventHandler delegate). Unlike the delegates created by the type library importer, these signatures do not match the signatures of methods in the source interface. Instead, the delegate signature always has two parameters to (almost) match the convention used by all delegates in Windows Forms. The first parameter is a System.Object type named sender. The second parameter, named e, is a type described next in the list.

  • SourceInterfaceName_MethodNameEvent—A class with a public field representing each parameter of a source interface method. One of these classes exists for each method on the source interface that has one or more arguments. This is the second e parameter used in each delegate signature, but fails to conform to .NET guidelines in two ways: the class does not derive from System.EventArgs and it does not have an EventArgs suffix.

  • AxCoClassNameEventMulticaster—A public sink class that implements the source interface. This serves a similar role as the sink helper class generated by the type library importer.

No event interfaces are generated because the AxCoClassName class is always used directly. There's also no separate event provider class because that functionality is merged into the AxCoClassName class.

DIGGING DEEPER

The reason a separate event provider class is generated by the type library importer is that classes marked with the ComImportAttribute pseudo-custom attribute cannot contain any implementation. None of the classes in an ActiveX Assembly are marked with this attribute because they don't directly represent COM types.

Listing 11 shows snippets of C# code inspired by the code obtained by running the ActiveX importer on the file containing the Microsoft Internet Controls type library, for example:

aximp C:\Windows\System32\shdocvw.dll /source

This type library contains the WebBrowser control introduced in Chapter 3:

[
 uuid(8856F961-340A-11D0-A96B-00C04FD705A2),
 helpstring("WebBrowser Control"),
 control
]
coclass WebBrowser {
 [default] interface IWebBrowser2;
 interface IWebBrowser;
 [default, source] dispinterface DWebBrowserEvents2;
 [source] dispinterface DWebBrowserEvents;
};

The WebBrowser coclass has the same two source interfaces as the InternetExplorer coclass used throughout this chapter, so you can easily compare how events are handled with imported ActiveX controls to how they are handled with plain imported coclasses.

Listing 11—Some of the Types and Members Generated by the ActiveX Importer for Event Support

 1: [AxHost.ClsidAttribute("{8856f961-340a-11d0-a96b-00c04fd705a2}")]
 2: [DesignTimeVisibleAttribute(true)]
 3: [DefaultProperty("Name")]
 4: public class AxWebBrowser : AxHost
 5: {
 6:  private SHDocVw.IWebBrowser2 ocx;
 7:  private AxWebBrowserEventMulticaster eventMulticaster;
 8:  private AxHost.ConnectionPointCookie cookie;
 9:  ...
10:  public event System.EventHandler DownloadBegin;
11:  public event DWebBrowserEvents2_CommandStateChangeEventHandler
12:   CommandStateChange;
13:  ...
14:  protected override void CreateSink()
15:  {
16:   try
17:   {
18:    this.eventMulticaster = new AxWebBrowserEventMulticaster(this);
19:    this.cookie = new AxHost.ConnectionPointCookie(this.ocx,
20:     this.eventMulticaster, typeof(SHDocVw.DWebBrowserEvents2));
21:   }
22:   catch (System.Exception) {}
23:  }
24:
25:  protected override void DetachSink()
26:  {
27:   try {
28:    this.cookie.Disconnect();
29:   }
30:   catch (System.Exception) {}
31:  }
32:  ...
33:  internal void RaiseOnDownloadBegin(object sender, System.EventArgs e)
34:  {
35:   if (this.DownloadBegin != null)
36:    this.DownloadBegin(sender, e);
37:  }
38:
39:  internal void RaiseOnCommandStateChange(object sender,
40:   DWebBrowserEvents2_CommandStateChangeEvent e)
41:  {
42:   if (this.CommandStateChange != null)
43:    this.CommandStateChange(sender, e);
44:  }
45:  ...
46: }
47: ...
48: public delegate void DWebBrowserEvents2_CommandStateChangeEventHandler(
49:  object sender, DWebBrowserEvents2_CommandStateChangeEvent e);
50:
51: public class DWebBrowserEvents2_CommandStateChangeEvent
52: {
53:  public int command;
54:  public bool enable;
55:
56:  public DWebBrowserEvents2_CommandStateChangeEvent(
57:   int command, bool enable)
58:  {
59:   this.command = command;
60:   this.enable = enable;
61:  }
62: }
63: ...
64: public class AxWebBrowserEventMulticaster : SHDocVw.DWebBrowserEvents2
65: {
66:  private AxWebBrowser parent;
67:
68:  public AxWebBrowserEventMulticaster(AxWebBrowser parent)
69:  {
70:   this.parent = parent;
71:  }
72:  ...
73:  public virtual void DownloadBegin()
74:  {
75:   System.EventArgs downloadbeginEvent = new System.EventArgs();
76:   this.parent.RaiseOnDownloadBegin(this.parent, downloadbeginEvent);
77:  }
78:
79:  public virtual void CommandStateChange(int command, bool enable)
80:  {
81:   DWebBrowserEvents2_CommandStateChangeEvent commandstatechangeEvent =
82:    new DWebBrowserEvents2_CommandStateChangeEvent(command, enable);
83:   this.parent.RaiseOnCommandStateChange(this.parent,
84:    commandstatechangeEvent);
85:  }
86:  ...
87: }

The snippets of the AxWebBrowser class shown focus on two events and their supporting types and members—DownloadBegin and CommandStateChange. Lines 10–12 define the two events. Because the DownloadBegin source interface method has no parameters, the simple System.EventHandler delegate is used rather than defining a new DWebBrowserEvents2_DownloadBeginEventHandler delegate with the same signature. The CommandStateChange source interface method does have parameters, so a specific delegate type is used with this event.

The CreateSink and DetachSink methods in Lines 14–31 connect and disconnect the connection point for the object's default source interface. CreateSink is invoked when the control's System.ComponentModel.ISupportInitialize.EndInit implementation is called, as is done inside the Visual Studio .NET-generated InitializeComponent method when a Windows Forms control is dragged onto a form in the designer. DetachSink is invoked inside the control's IDisposable.Dispose implementation. Keep this in mind in case you're using an ActiveX control that's picky about when its default connection point is used (as in the TAPI example earlier in the chapter). If some custom initialization routine must be called first, you'd need to insert a call to it somewhere in-between the control's instantiation and the call to EndInit, which would unfortunately be inside the designer-generated InitializeComponent method that you're not supposed to touch.

The RaiseOn... methods, one per event, are defined in Lines 33–44 so that the event multicaster class, defined later, has access to raising the events. Lines 48–62 contain the pair of delegate and quasi-EventArgs class for the event that doesn't use the standard System.EventArgs delegate. Besides not having the EventArgs suffix and not deriving from System.EventArgs, the event argument classes generated by the ActiveX importer have another oddity that goes against .NET conventions—every field name is lowercase, even if the original parameters in the source interface method were uppercase (as were Command and Enable). The constructor in Lines 56–61 simply provides a convenient means for setting all of the class's fields.

TIP

To minimize confusion when using types generated by the ActiveX importer and to conform to .NET guidelines, it might be a good idea to modify the types produced. This can easily be done using AXIMP.EXE's /source option to generate C# source code for the ActiveX assembly. Before compiling the generated source, you can rename the ...Event classes to ...EventArgs classes, perhaps capitalize the public fields of these classes, and make them derive from System.EventArgs. Another user-friendly change would be to rename the delegate types from SourceInterfaceName_MethodNameEventHandler to simply MethodNameEventHandler, as long as the name doesn't conflict with others.

When compiling source code generated from AXIMP.EXE, you'll need to reference the corresponding Interop Assembly, System.Windows.Forms.dll, and System.dll.

Finally, the AxWebBrowserEventMulticaster class in Lines 64–87 is the event sink that implements the default source interface, receives the callbacks, and raises the .NET event to anyone who may be listening.

TIP

Besides renaming types, you can take advantage of ActiveX importer-generated source code to make changes that can add functionality. A good example of this would be to add the code necessary to handle non-default source interfaces just as the default source interface is currently handled.

Using ActiveX Events

To conclude this chapter, we'll update the Web Browser example from Listing 3.4 in Chapter 3 with event support. We'll not only fix the behavior of the Back and Forward buttons to be implemented the way the ActiveX control intended, but add a history list and a log of all events. The final product is pictured in Figure 4.

Figure 4 The event-enabled .NET Web browser.

Inside Visual Studio .NET, the easiest way to add event handlers to an event is to click on the Events lightning bolt in the property browser, then double-click on any events you wish to handle. An empty method signature and the appropriate event hooking and unhooking code are then emitted for you. Figure 5 displays the events for the WebBrowser control. When displayed in categorized mode, the events originating from COM can easily be identified because they fall under the Misc category and have no description in the lower pane.

Figure 5 The Visual Studio .NET property browser showing events.

Listing 12 shows the important parts of the source code for the updated example. The full source code is available in C# and Visual Basic .NET on this book's Web site.

Listing 12—Using Events on a Hosted ActiveX Control

 1: using System;
 2: using SHDocVw;
 3: using AxSHDocVw;
 4: using System.Windows.Forms;
 5:
 6: public class MyWebBrowser : Form
 7: {
 8:  private System.ComponentModel.IContainer components;
 9:  private AxWebBrowser axWebBrowser1;
 10:  ...
 11:  // Used for any optional parameters
 12:  private object m = Type.Missing;
 13:
 14:  public MyWebBrowser()
 15:  {
 16:   // Required for Windows Form Designer support
 17:   InitializeComponent();
 18:   axWebBrowser1.GoHome();
 19:  }
 20:  ...
 21:  private void InitializeComponent()
 22:  {
 23:   ...
 24:   axWebBrowser1.StatusTextChange += new
 25:    DWebBrowserEvents2_StatusTextChangeEventHandler(
 26:    axWebBrowser1_StatusTextChange);
 27:   axWebBrowser1.CommandStateChange += new
 28:    DWebBrowserEvents2_CommandStateChangeEventHandler(
 29:    axWebBrowser1_CommandStateChange);
 30:   axWebBrowser1.TitleChange += new
 31:    DWebBrowserEvents2_TitleChangeEventHandler(
 32:    axWebBrowser1_TitleChange);
 33:   axWebBrowser1.NavigateComplete2 += new
 34:    DWebBrowserEvents2_NavigateComplete2EventHandler(
 35:    axWebBrowser1_NavigateComplete2);
 36:   axWebBrowser1.ProgressChange += new
 37:    DWebBrowserEvents2_ProgressChangeEventHandler(
 38:    axWebBrowser1_ProgressChange);
 39:   ...
 40:  }
 41:  ...
 42:  private void axWebBrowser1_CommandStateChange(object sender,
 43:   DWebBrowserEvents2_CommandStateChangeEvent e)
 44:  {
 45:   eventList.Items.Add("CommandStateChange: command=" + e.command +
 46:    ", enable=" + e.enable).EnsureVisible();
 47:
 48:   if (e.command == (int)CommandStateChangeConstants.CSC_NAVIGATEBACK)
 49:   {
 50:    // Toggle the state of the "Back" button
 51:    toolBar1.Buttons[0].Enabled = e.enable;
 52:   }
 53:   else if (e.command ==
 54:    (int)CommandStateChangeConstants.CSC_NAVIGATEFORWARD)
 55:   {
 56:    // Toggle the state of the "Forward" button
 57:    toolBar1.Buttons[1].Enabled = e.enable;
 58:   }
 59:  }
 60:  ...
 61:  private void axWebBrowser1_NavigateComplete2(object sender,
 62:   DWebBrowserEvents2_NavigateComplete2Event e)
 63:  {
 64:   navigateBox.Text = e.uRL.ToString();
 65:   historyList.Items.Add(e.uRL);
 66:   eventList.Items.Add("NavigateComplete2: " + e.uRL).EnsureVisible();
 67:  }
 68:  ...
 69:  private void axWebBrowser1_ProgressChange(object sender,
 70:   DWebBrowserEvents2_ProgressChangeEvent e)
 71:  {
 72:   progressBar1.Maximum = e.progressMax;
 73:   progressBar1.Value = e.progress;
 74:   eventList.Items.Add("ProgressChange: " + e.progress + " out of " +
 75:    e.progressMax).EnsureVisible();
 76:  }
 77:  ...
 78:  private void axWebBrowser1_StatusTextChange(object sender,
 79:   DWebBrowserEvents2_StatusTextChangeEvent e)
 80:  {
 81:   statusBar1.Text = e.text;
 82:   eventList.Items.Add("StatusTextChange: " + e.text).EnsureVisible();
 83:  }
 84:
 85:  private void axWebBrowser1_TitleChange(object sender,
 86:   DWebBrowserEvents2_TitleChangeEvent e)
 87:  {
 88:   this.Text = e.text;
 89:   eventList.Items.Add("TitleChange: " + e.text).EnsureVisible();
 90:  }
 91:  ...
 92:  private void toolBar1_ButtonClick(object sender,
 93:   ToolBarButtonClickEventArgs e)
 94:  {
 95:   if (e.Button.Text == "Back")
 96:   {
 97:    axWebBrowser1.GoBack();
 98:   }
 99:   else if (e.Button.Text == "Forward")
100:   {
101:    axWebBrowser1.GoForward();
102:   }
103:   else if (e.Button.Text == "Stop")
104:   {
105:    axWebBrowser1.Stop();
106:   }
107:   else if (e.Button.Text == "Refresh")
108:   {
109:    axWebBrowser1.CtlRefresh();
110:   }
111:   else if (e.Button.Text == "Home")
112:   {
113:    axWebBrowser1.GoHome();
114:   }
115: }
116:
117: private void goButton_Click(object sender, System.EventArgs e)
118: {
119:  axWebBrowser1.Navigate(navigateBox.Text, ref m, ref m, ref m, ref m);
120: }
121: }

The first difference between this listing and the corresponding listing in Chapter 3 is in Lines 24–38. This shows a sampling of some of the events being handled. This code is automatically generated by Visual Studio .NET when double-clicking on events in the property browser.

Lines 42–90 show all of the interesting events that do something other than add information to the log. The implementation of the CommandStateChange event handler in Lines 42–59 first adds some information to the eventList log, which is a ListView control. Then, it toggles the state of either the Back or Forward button based on the information passed into the event. The NavigateComplete2 event handler in Lines 61–67 updates the TextBox control with the current URL and adds it to the history list, a ListBox control. Notice how the lowercase transformations done by the ActiveX importer produces a funny looking field called uRL!

The ProgressChange event handler in Lines 69–76 uses the passed-in information to control the form's ProgressBar control, and the StatusTextChange event handler in Lines 78–83 updates the form's StatusBar control with the passed-in text. Finally, the TitleChange event handler in Lines 85–90 updates the form's caption with the title of the current Web page.

The updated toolBar1_ButtonClick implementation now simply calls the methods corresponding to each button's action. The calls to GoBack and GoForward no longer need to be wrapped inside exception handling because the user shouldn't be able to click these buttons when there are no more pages in the list. (If an exception were to occur, it would be a problem that we'd want to know about.)

InformIT Promotional Mailings & Special Offers

I would like to receive exclusive offers and hear about products from InformIT and its family of brands. I can unsubscribe at any time.

Overview


Pearson Education, Inc., 221 River Street, Hoboken, New Jersey 07030, (Pearson) presents this site to provide information about products and services that can be purchased through this site.

This privacy notice provides an overview of our commitment to privacy and describes how we collect, protect, use and share personal information collected through this site. Please note that other Pearson websites and online products and services have their own separate privacy policies.

Collection and Use of Information


To conduct business and deliver products and services, Pearson collects and uses personal information in several ways in connection with this site, including:

Questions and Inquiries

For inquiries and questions, we collect the inquiry or question, together with name, contact details (email address, phone number and mailing address) and any other additional information voluntarily submitted to us through a Contact Us form or an email. We use this information to address the inquiry and respond to the question.

Online Store

For orders and purchases placed through our online store on this site, we collect order details, name, institution name and address (if applicable), email address, phone number, shipping and billing addresses, credit/debit card information, shipping options and any instructions. We use this information to complete transactions, fulfill orders, communicate with individuals placing orders or visiting the online store, and for related purposes.

Surveys

Pearson may offer opportunities to provide feedback or participate in surveys, including surveys evaluating Pearson products, services or sites. Participation is voluntary. Pearson collects information requested in the survey questions and uses the information to evaluate, support, maintain and improve products, services or sites, develop new products and services, conduct educational research and for other purposes specified in the survey.

Contests and Drawings

Occasionally, we may sponsor a contest or drawing. Participation is optional. Pearson collects name, contact information and other information specified on the entry form for the contest or drawing to conduct the contest or drawing. Pearson may collect additional personal information from the winners of a contest or drawing in order to award the prize and for tax reporting purposes, as required by law.

Newsletters

If you have elected to receive email newsletters or promotional mailings and special offers but want to unsubscribe, simply email information@informit.com.

Service Announcements

On rare occasions it is necessary to send out a strictly service related announcement. For instance, if our service is temporarily suspended for maintenance we might send users an email. Generally, users may not opt-out of these communications, though they can deactivate their account information. However, these communications are not promotional in nature.

Customer Service

We communicate with users on a regular basis to provide requested services and in regard to issues relating to their account we reply via email or phone in accordance with the users' wishes when a user submits their information through our Contact Us form.

Other Collection and Use of Information


Application and System Logs

Pearson automatically collects log data to help ensure the delivery, availability and security of this site. Log data may include technical information about how a user or visitor connected to this site, such as browser type, type of computer/device, operating system, internet service provider and IP address. We use this information for support purposes and to monitor the health of the site, identify problems, improve service, detect unauthorized access and fraudulent activity, prevent and respond to security incidents and appropriately scale computing resources.

Web Analytics

Pearson may use third party web trend analytical services, including Google Analytics, to collect visitor information, such as IP addresses, browser types, referring pages, pages visited and time spent on a particular site. While these analytical services collect and report information on an anonymous basis, they may use cookies to gather web trend information. The information gathered may enable Pearson (but not the third party web trend services) to link information with application and system log data. Pearson uses this information for system administration and to identify problems, improve service, detect unauthorized access and fraudulent activity, prevent and respond to security incidents, appropriately scale computing resources and otherwise support and deliver this site and its services.

Cookies and Related Technologies

This site uses cookies and similar technologies to personalize content, measure traffic patterns, control security, track use and access of information on this site, and provide interest-based messages and advertising. Users can manage and block the use of cookies through their browser. Disabling or blocking certain cookies may limit the functionality of this site.

Do Not Track

This site currently does not respond to Do Not Track signals.

Security


Pearson uses appropriate physical, administrative and technical security measures to protect personal information from unauthorized access, use and disclosure.

Children


This site is not directed to children under the age of 13.

Marketing


Pearson may send or direct marketing communications to users, provided that

  • Pearson will not use personal information collected or processed as a K-12 school service provider for the purpose of directed or targeted advertising.
  • Such marketing is consistent with applicable law and Pearson's legal obligations.
  • Pearson will not knowingly direct or send marketing communications to an individual who has expressed a preference not to receive marketing.
  • Where required by applicable law, express or implied consent to marketing exists and has not been withdrawn.

Pearson may provide personal information to a third party service provider on a restricted basis to provide marketing solely on behalf of Pearson or an affiliate or customer for whom Pearson is a service provider. Marketing preferences may be changed at any time.

Correcting/Updating Personal Information


If a user's personally identifiable information changes (such as your postal address or email address), we provide a way to correct or update that user's personal data provided to us. This can be done on the Account page. If a user no longer desires our service and desires to delete his or her account, please contact us at customer-service@informit.com and we will process the deletion of a user's account.

Choice/Opt-out


Users can always make an informed choice as to whether they should proceed with certain services offered by InformIT. If you choose to remove yourself from our mailing list(s) simply visit the following page and uncheck any communication you no longer want to receive: www.informit.com/u.aspx.

Sale of Personal Information


Pearson does not rent or sell personal information in exchange for any payment of money.

While Pearson does not sell personal information, as defined in Nevada law, Nevada residents may email a request for no sale of their personal information to NevadaDesignatedRequest@pearson.com.

Supplemental Privacy Statement for California Residents


California residents should read our Supplemental privacy statement for California residents in conjunction with this Privacy Notice. The Supplemental privacy statement for California residents explains Pearson's commitment to comply with California law and applies to personal information of California residents collected in connection with this site and the Services.

Sharing and Disclosure


Pearson may disclose personal information, as follows:

  • As required by law.
  • With the consent of the individual (or their parent, if the individual is a minor)
  • In response to a subpoena, court order or legal process, to the extent permitted or required by law
  • To protect the security and safety of individuals, data, assets and systems, consistent with applicable law
  • In connection the sale, joint venture or other transfer of some or all of its company or assets, subject to the provisions of this Privacy Notice
  • To investigate or address actual or suspected fraud or other illegal activities
  • To exercise its legal rights, including enforcement of the Terms of Use for this site or another contract
  • To affiliated Pearson companies and other companies and organizations who perform work for Pearson and are obligated to protect the privacy of personal information consistent with this Privacy Notice
  • To a school, organization, company or government agency, where Pearson collects or processes the personal information in a school setting or on behalf of such organization, company or government agency.

Links


This web site contains links to other sites. Please be aware that we are not responsible for the privacy practices of such other sites. We encourage our users to be aware when they leave our site and to read the privacy statements of each and every web site that collects Personal Information. This privacy statement applies solely to information collected by this web site.

Requests and Contact


Please contact us about this Privacy Notice or if you have any requests or questions relating to the privacy of your personal information.

Changes to this Privacy Notice


We may revise this Privacy Notice through an updated posting. We will identify the effective date of the revision in the posting. Often, updates are made to provide greater clarity or to comply with changes in regulatory requirements. If the updates involve material changes to the collection, protection, use or disclosure of Personal Information, Pearson will provide notice of the change through a conspicuous notice on this site or other appropriate way. Continued use of the site after the effective date of a posted revision evidences acceptance. Please contact us if you have questions or concerns about the Privacy Notice or any objection to any revisions.

Last Update: November 17, 2020