Wtl guide for MFC programmers: Part VI-compatible ActiveX Controls

Source: Internet
Author: User
Tags blank page

Wtl guide for MFC programmers: Part VI-compatible ActiveX Controls

Original:Michael Dunn[Original ENGLISH]
Translation: Orbit [www.winmsg.com]

Download DEMO code

Content of this Chapter

  • Introduction
  • Start with the Wizard
    • Create a project
    • Automatically generated code
  • Use the resource editor to add controls
  • Controls used in ATL
    • Caxdialogimpl
    • Atlaxwin and caxwindow
  • Call the Control Method
  • Responds to events triggered by controls
    • Cmaindlg Modification
    • Enter the event ing Link
    • Compile event processing functions
  • Review example project
  • Create ActiveX Control at runtime
  • Handle Keyboard Events
  • Continue
  • Modify record

Introduction

In chapter 6, I will introduce how ATL supports ActiveX controls in the dialog box. as ActiveX controls are specialized in ATL, wtl does not add other auxiliary classes. However, the use of ActiveX controls in ATL is very different from that in MFC. I will introduce how to accommodate a control and handle control events. it is inconvenient to develop an ATL application than to use the MFC class wizard. In the wtl program, you can naturally Use ATL to support ActiveX Control inclusion.

The example project demonstrates how to use the IE browser control. I have two advantages when selecting a browser control:

  1. Each computer has this control, and
  2. It has many methods and events and is a good example for demonstration.

Of course, I cannot compare with those who spent a lot of time writing custom browsers Based on IE browser controls. However, after reading this article, you will know how to write your own customized browsers!

Start with the Wizard Create a project

The wtl wizard can create a program that supports ActiveX Control inclusion. I will start a new project named iehoster. We use the modeless dialog box as in the previous chapter, but this time we want to select enable ActiveX Control hosting, for example:

Selecting this check box will make our dialog box derived from caxdialogimpl, so that ActiveX control can be tolerated. On the second page of the wizard, there is a check box named inclusion ActiveX control. However, selecting this option does not affect the final result. Therefore, you can click "finish" on the first page to end the wizard.

Code generated by the wizard

In this section, I will introduce some new Code (generated by the wizard) that I have never seen before, and the next section will introduce the ActiveX inclusion class details.

The first file to be viewed is stdafx. H, which contains the following files:

#include <atlbase.h>#include <atlapp.h> extern CAppModule _Module; #include <atlcom.h>#include <atlhost.h>#include <atlwin.h>#include <atlctl.h>// .. other WTL headers ...

Atlcom. h and atlhost. h are very important. They contain definitions of com-related classes (such as smart pointer ccomptr) and window classes that can tolerate controls.

Next let's take a look at the cmaindlg class declared in maindlg. h:

class CMainDlg : public CAxDialogImpl<CMainDlg>,                 public CUpdateUI<CMainDlg>,                 public CMessageFilter, public CIdleHandler

Cmaindlg is now derived from the caxdialogimpl class, which is the first step to enable the dialog box to support ActiveX Control inclusion.

Finally, let's take a look at the newly added line of code in winmain:

int WINAPI _tWinMain(...){//...    _Module.Init(NULL, hInstance);     AtlAxWinInit();     int nRet = Run(lpstrCmdLine, nCmdShow);     _Module.Term();    return nRet;}

Atlaxwininit () registers a window class without atlaxwin. ATL uses it to create an inclusive window for ActiveX controls.

Use the resource editor to add controls

Like an MFC program, ATL can also use the resource editor to add controls to the dialog box. First, right-click the dialog box editor and select "insert ActiveX control" in the pop-up menu ":

VC displays the control installed by the system in a list. In the scrolling list, select "Microsoft Web Browser" and click "insert" to add the control to the dialog box. View the properties of the control and set the ID to idc_ie. The control display in the dialog box should look like this:

If you compile and run the program now, you will see the browser control in the dialog box, which will display a blank page, because we haven't told it where to go yet.

In the next section, I will introduce the ATL classes related to creating and accommodating ActiveX controls, and we will also understand how these classes exchange information with browsers.

Controls used in ATL

In the dialog box, ActiveX control requires two classes to work together: caxdialogimpl and caxwindow. They process the interface methods required by all control containers and provide common functions, such as querying a special COM interface of the control.

Caxdialogimpl

The first class is caxdialogimpl. To allow controls in your dialog box, you must derive from the caxdialogimpl class instead of the cdialogimpl class. The caxdialogimpl class overload the CREATE () and domodal () functions, which are called by the global functions atlaxcreatedialog () and atlaxdialogbox () respectively. Since the iehoster dialog box is created by create (), let's take a look at what atlaxcreatedialog () has done.

Atlaxcreatedialog () uses the helper class _ dialogsplithelper to load the resources of the dialog box. This helper class traverses the control of the dialog box and searches for the special portals created by the resource editor. These special portals indicate that this is an ActiveX control. For example, the following is the browser control entry in the iehoster. RC file:

CONTROL "",IDC_IE,"{8856F961-340A-11D0-A96B-00C04FD705A2}",        WS_TABSTOP,7,7,116,85

The first parameter is the window text (empty string), the second is the Control ID, and the third is the window class name. _ Dialogsplithelper: splitdialogtemplate () function is used to find the window class name starting. It creates a temporary dialog box template in the memory. In this new template, these special control entries are replaced by the created atlaxwin window. The new entry is the equivalent body in the memory:

CONTROL "{8856F961-340A-11D0-A96B-00C04FD705A2}",IDC_IE,"AtlAxWin",        WS_TABSTOP,7,7,116,85

The result is that an atlaxwin window with the same ID is created. The title of the window is the guid of the ActiveX control. Therefore, the value returned by calling getdlgitem (idc_ie) is the handle of the atlaxwin window rather than the ActiveX control.

After the splitdialogtemplate () function is complete, the atlaxcreatedialog () function then calls the createdialogindirectparam () function to use the modified template creation dialog box.

Atlaxwin and caxwindow

As mentioned above, atlaxwin is actually the host window of ActiveX Control. atlaxwin also uses a special window interface class: caxwindow. After atlaxwin creates a dialog box from the template, atlaxwin's window processing process, atlaxwindowproc (), processes the wm_create message and creates the corresponding ActiveX control. ActiveX controls can also be dynamically created during running without a dialog box template. I will introduce this method later.

The message processing function of wm_create calls the global function atlaxcreatecontrol () and passes the title of the atlaxwin window as a parameter to this function. You should remember that it is actually the guid of the browser control. Atlaxcreatecontrol () will call a bunch of other functions, but it will eventually use the createnormalizedobject () function, which converts the window title to guid and finally calls cocreateinstance () to create the ActiveX control.

As ActiveX controls are subwindows of atlaxwin, you cannot directly access controls in the dialog box. Of course, caxwindow provides these methods to communicate with controls. The most common method is querycontrol (), this method calls the QueryInterface () method of the control. For example, you can use querycontrol () to obtain the iwebbrowser2 interface from the browser control, and then use this interface to direct the browser to the specified URL.

Call the Control Method

Since our dialog box has a browser control, we can use the COM interface to interact with it. The first thing we do is to use the iwebbrowser2 interface to direct it to a new URL. In the oninitdialog () function, we associate a caxwindow variable with the atlaxwin of the inclusive control.

CAxWindow wndIE = GetDlgItem(IDC_IE);

Then declare an iwebbrowser2 interface pointer and query this interface of the browser control, using caxwindow: querycontrol ():

CComPtr<IWebBrowser2> pWB2;HRESULT hr;hr = wndIE.QueryControl ( &pWB2 );

Querycontrol () calls the QueryInterface () method of the browser control. If it succeeds, the iwebbrowser2 interface is returned. We can call navigate ():

    if ( pWB2 )        {        CComVariant v;  // empty variant         pWB2->Navigate ( CComBSTR("http://www.codeproject.com/"),                          &v, &v, &v, &v );        }

Responds to events triggered by controls

It is very easy to get an interface from a browser control. It can communicate with the control in one way. Controls usually communicate with the outside world in the form of events. Atl has dedicated class packaging connection points and events, so we can receive these events from the control. Four things are required for event support:

  1. Convert cmaindlg to com Object
  2. Add idispeventsimpleimpl to the inheritance list of cmaindlg
  3. Enter the event ing chain, which indicates which events need to be handled
  4. Compile the Event Response Function

Cmaindlg Modification

The reason for converting cmaindlg into a COM object is that the event is based on idispatch. to expose this interface to cmaindlg, it must be a COM object. Idispeventsimpleimpl provides the implementation of the idispatch interface and the processing function required to establish the connection point. When an event occurs, idispeventsimpleimpl also calls the processing function of the event we want to receive.

The following classes need to be added to the cmaindlg integration list, And com_map lists the interfaces exposed by cmaindlg:

#include <exdisp.h>    // browser control definitions#include <exdispid.h>  // browser event dispatch IDs class CMainDlg : public CAxDialogImpl<CMainDlg>,                 public CUpdateUI<CMainDlg>,                 public CMessageFilter, public CIdleHandler,                 public CComObjectRootEx<CComSingleThreadModel>,                 public CComCoClass<CMainDlg>,                 public IDispEventSimpleImpl<37, CMainDlg, &DIID_DWebBrowserEvents2>{...  BEGIN_COM_MAP(CMainDlg)    COM_INTERFACE_ENTRY2(IDispatch, IDispEventSimpleImpl)  END_COM_MAP()};

Ccomobjectrootex class ccomcoclass makes cmaindlg a COM object. The template parameter of idispeventsimpleimpl is the event ID, and our class name and the IID of the connection point interface. The event ID can be any positive number, and the IID of the connection point object is diid_dwebbrowserevents2. You can find these parameters in the relevant documents of the browser control, or you can view exdisp. h.

Enter the event ing Link

The next step is to add an event ing link to the cmaindlg. This ing link links the events we are interested in with our processing functions. The first event we want to see is downloadbegin. When the browser starts downloading a page, this event is triggered. In response to this event, "Please wait" information is displayed to the user, let the user know that the browser is busy. You can find the dwebbrowserevents2: downloadbegin event prototype in msdn.

  void DownloadBegin();

This event has no parameters and does not require a return value. To prototype the event into an event response chain, we need to write a _ atl_func_info structure that contains the return value, number of parameters, and parameter type. Because the event is based on idispatch, all parameters are represented by variant. The description of this data structure is quite long (many data types are supported). The following are common examples:

Vt_empty: void
Vt_bstr: String in BSTR format
Vt_i4: 4-byte signed integer, used for long-type parameters
Vt_dispatch: idispatch *
Vt_variant>: Variant
Vt_bool: variant_bool (valid values: variant_true and variant_false)

In addition, vt_byref indicates converting a parameter to a corresponding pointer. For example, vt_variant | vt_byref indicates the variant * type. The following is the definition of _ atl_func_info:

#define _ATL_MAX_VARTYPES 8 struct _ATL_FUNC_INFO{    CALLCONV cc;    VARTYPE  vtReturn;    SHORT    nParams;    VARTYPE  pVarTypes[_ATL_MAX_VARTYPES];};

Parameters:

CC
Our Event Response Function call method Convention. This parameter must be cc_stdcall, which indicates the _ stdcall method.
Vtreturn
Event Response Function Return Value Type
Nparams
Number of parameters in the event
Pvartypes
Corresponding parameter type, in the order from left to right

After understanding this, we can enter the _ atl_func_info structure for downloadbegin event processing:

_ATL_FUNC_INFO DownloadInfo = { CC_STDCALL, VT_EMPTY, 0 };

Now, back to the Event Response chain, we add a sink_entry_info macro for each event we want to process. The following is the macro for processing the downloadbegin event:

class CMainDlg : public ...{...  BEGIN_SINK_MAP(CMainDlg)    SINK_ENTRY_INFO(37, DIID_DWebBrowserEvents2, DISPID_DOWNLOADBEGIN,                    OnDownloadBegin, &DownloadInfo)  END_SINK_MAP()};

The macro parameter is the event ID (37, the same as the ID used in the idispeventsimpleimpl inheritance list), The IID of the event interface, the dispatch ID of the event (which can be found in msdn or exdispid. h header file), the name of the event handler and the pointer to the _ atl_func_info structure that describes the event processing.

Compile event processing functions

Okay, wait for such a long time (blow a whistle !), We can write the event processing function:

void __stdcall CMainDlg::OnDownloadBegin(){  // show "Please wait" here...}

Now let's look at a complex event, such as beforenavigate2. The prototype of this event is:

void BeforeNavigate2 (     IDispatch* pDisp, VARIANT* URL, VARIANT* Flags,    VARIANT* TargetFrameName, VARIANT* PostData,    VARIANT* Headers, VARIANT_BOOL* Cancel );

This method has seven parameters. For variant type parameters, we can check from msdn what type of data it passes. We are interested in URL, which is a BSTR type string.

The _ atl_func_info structure that describes the beforenavigate2 event is as follows:

_ATL_FUNC_INFO BeforeNavigate2Info =    { CC_STDCALL, VT_EMPTY, 7,        { VT_DISPATCH, VT_VARIANT|VT_BYREF, VT_VARIANT|VT_BYREF,          VT_VARIANT|VT_BYREF, VT_VARIANT|VT_BYREF, VT_VARIANT|VT_BYREF,          VT_BOOL|VT_BYREF }};

As before, the return value type is vt_empty, indicating no return value, and nparams is 7, indicating that there are 7 parameters. The following is an array of parameter types. These types are described earlier. For example, vt_dispatch indicates idispatch *.

The Event Response link entry is similar to the preceding example:

  BEGIN_SINK_MAP(CMainDlg)    SINK_ENTRY_INFO(37, DIID_DWebBrowserEvents2, DISPID_DOWNLOADBEGIN,                    OnDownloadBegin, &DownloadInfo)    SINK_ENTRY_INFO(37, DIID_DWebBrowserEvents2, DISPID_BEFORENAVIGATE2,                    OnBeforeNavigate2, &BeforeNavigate2Info)  END_SINK_MAP()

The event processing function looks like this:

void __stdcall CMainDlg::OnBeforeNavigate2 (    IDispatch* pDisp, VARIANT* URL, VARIANT* Flags,     VARIANT* TargetFrameName, VARIANT* PostData,     VARIANT* Headers, VARIANT_BOOL* Cancel ){CString sURL = URL->bstrVal;   // ... log the URL, or whatever you''d like ...}

I bet you are more and more fond of classwizard now, because when you insert an ActiveX control to the MFC dialog box, classwizard automatically completes all the work for you.

To convert a cmaindlg to an object, you must first modify the global function run (). Now, cmaindlg is a COM Object. We must use ccomobject to create cmaindlg:

int Run(LPTSTR /*lpstrCmdLine*/ = NULL, int nCmdShow = SW_SHOWDEFAULT){    CMessageLoop theLoop;    _Module.AddMessageLoop(&theLoop); CComObject<CMainDlg> dlgMain;     dlgMain.AddRef();     if ( dlgMain.Create(NULL) == NULL )        {        ATLTRACE(_T("Main dialog creation failed!/n"));        return 0;        }     dlgMain.ShowWindow(nCmdShow);     int nRet = theLoop.Run();     _Module.RemoveMessageLoop();    return nRet;}

Another alternative method is to use the ccomobject class instead of ccomobject and delete dlgmain. in the F () code line, the implementation of ccomobjectstack on the three methods of iunknown is somewhat insignificant (they are simply returned from the function ), because they are not necessary-such COM objects can ignore the reference count, because they are only temporary objects created in the stack.

Of course, this is not a perfect solution. ccomobjectstack is used for short-lived temporary objects. Unfortunately, any iunknown method that calls it will cause an asserted error. Because the cmaindlg object will call addref when listening for events, ccomobjectstack is not applicable to this situation.

To solve this problem, either use ccomobject or derive a ccomobjectstack2 class from ccomobjectstack, which allows calling the iunknow method. The unnecessary reference count of ccomobject is not serious-people don't notice it -- but if you have to save that CPU clock cycle, you can use the ccomobjectstack2 class in the example project code in this chapter.

Review example project

Now we have seen how the Event Response works. Let's take a look at the complete iehoster project. It contains a browser control and responds to six events. It also displays an event list, you will have a perceptual knowledge about how browsers use them to provide interfaces with progress bars. The program handles the following events:

  • Beforenavigate2 and navigatecomplete2: these events allow the program to control URL navigation. If you have responded to the beforenavigate2 event, you can cancel the navigation in the event processing function.
  • Downloadbegin and downloadcomplete: The program uses these events to control "wait" messages, which indicates that the browser is working. A better program will use an animation like ie during this period.
  • Commandstatechange: This event tells the program when the forward and backward navigation commands are available, and the application changes the corresponding buttons to available or unavailable.
  • Statustextchange: this event is triggered in several situations, for example, moving the cursor over a hyperlink. This event sends a string, the application responds to this event, and displays the string on the Static Control in the browser window.

The Program has four buttons to control the work of the browser: backward, forward, stop, and refresh. They call the corresponding methods of iwebbrowser2 respectively.

Both events and data sent along with the event are recorded in the list control. You can see the event trigger. You can also close some event records and only observe one of them. To demonstrate the important role of event processing, we check the URL in the beforenavigate2 event processing function. If "doubleclick.net" is found, the navigation is canceled. Some IE Plug-ins such as the ad and pop-up window Filters use this method instead of the HTTP proxy. The following is the code for these checks.

void __stdcall CMainDlg::OnBeforeNavigate2 (    IDispatch* pDisp, VARIANT* URL, VARIANT* Flags,     VARIANT* TargetFrameName, VARIANT* PostData,     VARIANT* Headers, VARIANT_BOOL* Cancel ){USES_CONVERSION;CString sURL;     sURL = URL->bstrVal;     // You can set *Cancel to VARIANT_TRUE to stop the     // navigation from happening. For example, to stop     // navigates to evil tracking companies like doubleclick.net:    if ( sURL.Find ( _T("doubleclick.net") ) > 0 )        *Cancel = VARIANT_TRUE;}

The following figure shows how our program works:

Iehoster also uses the classes introduced in the previous chapters: cbitmapbutton (for browser control buttons), clistviewctrl (for Event Recording), DDX (for tracking checkbox status), and cdialogresize.

Create ActiveX Control at runtime

The resource editor can also be used to dynamically create ActiveX controls during running. The about dialog box demonstrates this technology. In the dialog box editor, a group box is pre-placed for positioning browser controls:

In the oninitdialog () function, we use caxwindow to create a new atlaxwin, which is located at the position of the pre-placed group box (This group box is subsequently destroyed ):

LRESULT CAboutDlg::OnInitDialog(...){CWindow wndPlaceholder = GetDlgItem ( IDC_IE_PLACEHOLDER );CRect rc;CAxWindow wndIE;     // Get the rect of the placeholder group box, then destroy     // that window because we don''t need it anymore.    wndPlaceholder.GetWindowRect ( rc );    ScreenToClient ( rc );    wndPlaceholder.DestroyWindow();     // Create the AX host window.    wndIE.Create ( *this, rc, _T(""),                    WS_CHILD | WS_VISIBLE | WS_CLIPCHILDREN );

Next we will use the caxwindow method to create an ActiveX control. There are two methods available: createcontrol () and createcontrolex (). Createcontrolex () returns an interface pointer with an additional parameter, so that you do not need to call the querycontrol () function. The two parameters we are interested in are the first and fourth parameters. The first parameter is the guid of the browser control in string form, and the fourth parameter is an iunknown * type pointer, this Pointer Points to the iunknown interface of the ActiveX control. After creating the control, you can query the iwebbrowser2 interface and then control it to a URL as before.

CComPtr<IUnknown> punkCtrl;CComQIPtr<IWebBrowser2> pWB2;CComVariant v;     // Create the browser control using its GUID.    wndIE.CreateControlEx ( L"{8856F961-340A-11D0-A96B-00C04FD705A2}",                             NULL, NULL, &punkCtrl );     // Get an IWebBrowser2 interface on the control and navigate to a page.    pWB2 = punkCtrl;    pWB2->Navigate ( CComBSTR("about:mozilla"), &v, &v, &v, &v );}

For ActiveX controls with progid, you can pass progid to createcontrolex () instead of guid. For example, you can create a browser control as follows:

// Use the progid of the control to create shell. Explorer: wndie. createcontrolex (L "shell. Explorer", null, null, & punkctrl );

Createcontrol () and createcontrolex () also have some overload functions for some special cases where browsers are used. If your application uses web pages as HTML resources, you can use the resource ID as the first parameter. ATL will create a browser control and navigate to the resource. Iehoster contains a Web page resource with ID idr_aboutpage. We use the code in the about dialog box to display this page:

    wndIE.CreateControl ( IDR_ABOUTPAGE );

This is the result:

The sample code is used for the three methods mentioned above. You can view the comments and uncommented codes in caboutdlg: oninitdialog () to see how they work.

Handle Keyboard Events

The last but important detail is the Keyboard Message. The keyboard processing of ActiveX controls is very complex, because the controls and their host programs must work together to ensure that the controls can see messages of interest to them. For example, the browser control allows you to use the tab key to switch between links. MFC handles all the work on its own, so you will never realize how much work is required to make the keyboard perfect and correct.

Unfortunately, the wizard does not generate a keyboard processing code for the dialog box-based program. Of course, if you use form view as the SDI Program of the View class, you will see that the necessary code has been added to pretranslatemessage. When the program obtains a mouse or Keyboard Message from the message queue, it uses the ATL wm_forwardmsg message to pass the message to the control with the current focus. They generally do nothing, but if it is an ActiveX control, the wm_forwardmsg message is finally sent to the atlaxwin that contains the control, atlaxwin recognizes the wm_forwardmsg message and takes necessary measures to check whether the control needs to process the message in person.

If the window with focus does not recognize the wm_forwardmsg message, pretranslatemessage () will then call the isdialogmessage () function, so that the navigation key of the standard dialog box such as Tab can work normally.

The pretranslatemessage () function of the example project contains the required code. Because pretranslatemessage () is only valid in the dialog box without mode, therefore, if you want to use the keyboard correctly in a dialog box-based application, you must use the non-mode dialog box.Continue

In the next chapter, we will return to the Framework Window and show you how to use the separation window.

Modify record

May 20,200 3: the first release of the article.

Contact Us

The content source of this page is from Internet, which doesn't represent Alibaba Cloud's opinion; products and services mentioned on that page don't have any relationship with Alibaba Cloud. If the content of the page makes you feel confusing, please write us an email, we will handle the problem within 5 days after receiving your email.

If you find any instances of plagiarism from the community, please send an email to: info-contact@alibabacloud.com and provide relevant evidence. A staff member will contact you within 5 working days.

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.