WTL for MFC Programmers, Part Vi - Hosting ActiveX Controls

xiaoxiao2021-03-06  92

WTL for MFC Programmers, Part Vi - Hosting ActiveX Controls

Original: Michael Dunn [English] Translation: Orbit (Star Rails Orbit) [http://www.winmsg.com/cn/orbit.htm]

Download Demonstration Code

This chapter

Introduction starts from the use wizard

Establish an auto-generated code using the Resource Editor Add control ATL to use the control class

CAXDIALOGIMPL Atlaxwin and CaxWindow call control method Response control triggered events

CMAINDLG's Modification Fill in the event mapping chain to write event handlers Recall the example of engineering runtime Create an ActiveX control keyboard event handling Continue to modify the record

Introduction

In Chapter 6, I will introduce the ATL to use the ActiveX control in the dialog, because the ActiveX control is the major of ATL, so WTL does not add other secondary classes. However, using ACTIVEX controls in ATL is very different from MFC, it needs to be introduced. I will introduce how to include an control and handle the control's event, and develop ATL applications to be a bit inconvenient for the MFC class wizard. The ATL can be used in the WTL program.

Example Project Demonstrate How to use IE browser controls, I have two benefits of browser controls:

Each computer has this control, and it has many methods and events, which is a good example for doing demonstrations.

I certainly can't compare with those who spend a lot of time-based custom browsers, but when you finish this article, you know how to start writing your custom browser!

Start with the use wizard

Create project

WTL's wizard can create a program that supports an ActiveX control, I will start a new project called IEHOSTER. We use the Non-Mode dialog as the previous chapter, just this time you have to select Enable ActiveX Control Hosting, as shown below:

Select this Check Box will make our dialog to derive from CaxDialogIMPL, so you can get an ActiveX control. On the second page of the wizard, there is a Check Box called an accommodation ActiveX control, but select this seems to have no effect on the last result, so you can click the "Finish" end wizard on the first page.

Wizard creative code

In this section I will introduce some new code that I have previously seen (generated by the wizard), the next section introduces the details of the ActiveX tolerance class.

The first thing you want to see is stdafx.h, which contains these files:

#include #include extern capModule _Module; #include #include #include #include // .. Other WTL Headers ...

ATLCOM.H and ATLHOST.H are two important, which contain some of COM-related classes (such as smart pointer ccomptr), as well as window classes that can be incorporated.

Next, look at the CMAINDLG class declared in MAINDLG.H:

Class CMAINDLG: Public CaxDialogImpl , public cupdateui , public cMessageFilter, Public CidleHandler

CMAINDLG is now derived from the CaxDialogImpl class, which is the first step to enable the dialog to accommodate the ActiveX control. Finally, look at the new 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 that is not atlaxwin, ATL uses it to create an Accommoding window of the ActiveX control.

Add controls using resource editor

Like the MFC program, ATL can also add controls to the dialog using the Resource Editor. First, click on the right button on the dialog editor, select "Insert ActiveX Control" in the pop-up menu:

VC Displays the controlled controls in a list, scroll the list to select "Microsoft Web Browser", click the Insert button to join the control into the dialog. Check the properties of the control, set the ID to IDC_IE. The control display in the dialog should look like this:

If you compile the running program now, you will see the browser control in the dialog, it will display a blank page, because we haven't told it to go.

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

The class of the control in ATL

Using the ActiveX control in the dialog requires two classes to work together: CAXDIALOGIMPL and CAXWINDOW. They handle interface methods that all control containers must implement, providing a generic function, such as a particular COM interface for the query control.

CAXDIALOGIMPL

The first class is CAXDIALOGIMPL, your dialog box must be able to derive from the CaxDialogImpl class, not from the CDialogImpl class. The CAXDialogIMPL class is overloaded with create () and domodal () functions, which are called by global functions atlaxcreatedialog () and AtlaxDialogbox (). Since the IEHOSTER dialog is created by Create (), let's take a look at what works at ATLAXCREATEDIALOG ().

AtlaXcreatedialog () uses a secondary _dialogsplithelper load dialog resource, this secondary class is traversed by controls of the dialog box, finds a special entry created by the resource editor, which means this is an ActiveX control. For example, the following is the entry of the browser control in the IEHOSTER.RC file:

"" ", 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 ID of the control, the third is the class name of the window. _Dialogsplithelper :: splitdialogtemplate () function finds that this is an an ActiveX control when the window class name starting with '{'. It creates a temporary dialog template in memory, and the Atlaxwin window created in this new template is replaced, the new entry is an equivalent in memory:

Control "{8856F961-340A-11D0-A96B-00c04FD705A2}", IDC_IE, "Atlaxwin", WS_TABSTOP, 7, 7, 116, 85 The result is a AtlaxWin window for the same ID, the title of the window is the GUID of the ActiveX control. So you call getDLGITEM (IDC_IE) The value returned by the Atlaxwin window instead of the ActiveX control itself.

After the splitdialogtemplate () function is completed, AtlaxCreatedialog () then calls the createDialogindirectParam () function to create a dialog box after the modified template.

Atlaxwin and CaxWindow

As mentioned above, Atlaxwin is actually a host window of the ActiveX control. Atlaxwin also uses a special window interface class: CAXWINDOW, after AtlaxWin creates a dialog, AtlaxWin's window processing, atlaxwindowproc (), The WM_CREATE message is dealt with and the corresponding ActiveX control will be created. The ActiveX control can also be created during running, and do not need a dialog template, I will introduce this method later.

WM_CREATE message processing function calls global function AtlaXcreateControl (), passes the window title of the Atlaxwin window to the function, you should remember that the actual GUID of the browser control. AtlaxCreateControl () will call a bunch of other functions, but ultimately use the CreatenormalizedObject () function, this function converts the window title into a GUID, and finally calls cocreateInstance () Create an ActiveX control.

Since the ActiveX control is a sub-window of AtlaxWin, the dialog box cannot directly access the control, and of course the CaxWindow provides these method control communication, the most common one is queryControl (), this method calls the QueryInterface () method of the control. For example, you can use queryControl () to get the iWebBrowser2 interface from the browser control, then use this interface to boot the browser to the specified URL.

Method for calling controls

Since our dialog has a browser control, we can use the COM interface to interact with it. We do the first thing is to guide it to a new URL using the iWebBrowser2 interface. In the OnInitDialog () function, we link a caxwindow variable to 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, use caxwindow :: queryControl ():

CComptr PWB2; HRESULT HR; HR = WNDIE.QUERYCONTROL (& PWB2);

QueryControl () calls the queryinterface () method for the browser control, if success, return to the iWebBrowser2 interface, we can call navigate ():

IF (PWB2) {ccomvariant v; // Empty Variant PWB2-> NaviGate ("http://www.codeproject.com/"), & V, & V, & V, & V);} Response control triggered events

The interface from the browser control is very simple, via it can communicate with the control. Usually the control will also communicate with external communication in the form of an event, ATL has a dedicated class package connection point and event, so we can receive these events from the control. For the use of support for events, you need to do four things:

Camaindlg becomes CMAINDLG to add iDispeventsImpleImpl to the inheritance list of cmaindlg to fill in the event mapping chain, indicating which events need to process the writing event response function

CMAINDLG modification

The reason why CMAINDLG transfers into a COM object is that the event is based on iDispatch, in order to expose CMAINDLG to this interface, it must be a COM object. IdispeventSIMPLEIMPL provides the IdisPatch interface implementation and the processing function you need to establish a connection point. When the event occurs, IdispeventSIMPLEIMPL also calls the handler of the event we want to receive.

The following classes need to be added to the cmaindlg integrated list, while com_map lists the exposed interfaces of CMAINDLG:

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

CCOMOBJECTROTEX CCOMCOCLASS Co-CMAINDLG a CMAINDLG, the template parameter of IdispeventsImpleImpl is the id of the event, our class name, and connection point interface IID. The event ID can be arbitrary positive, the IID of the connection point object is DIID_DWEBBROWSEREVENTS2, which can find these parameters in the relevant documentation of the browser control, or view EXDISP.H.

Fill in the event mapping chain

The next step is to add an event mapping chain to the CMAINDLG, which links us of interest to our handles and our processing functions. The first event we have to see is Downloadbegin. When the browser starts to download a page, we will trigger this event. We respond to this event to display "please wait" information to the user, so that users know that the browser is busy. In MSDN, you can find the prototype of DwebBBROWSEREvents2 :: Downloadbegin events.

Void Downloadbegin ();

This event does not have a parameter, nor does it need to return a value. In order to convert the prototype of this event into an event response chain, we need to write a _ATL_FUNC_INFO structure, which contains the number of returns, parameters, and parameter types. Since the event is iDispatch based, all parameters are represented by Variant, and the description of this data structure is relatively long (supporting a number of data types), the following is a commonly used:

VT_EMPTY: VOIDVT_BSTR: BSTR format strings VT_i4: 4 bytes are symbol integers, parameters for long type vt_dispatch: idispatch * vt_variant>: variantvt_bool: variant_bool (Allowed Value is Variant_true and Variant_FALSE)

In addition, the flag vt_byref indicates that a parameter is converted into a corresponding pointer. For example, vt_variant | vt_byref represents a Variant * type. Here is the definition of _ATL_FUNC_INFO:

#define _tl_max_vartypes 8 struct _atl_func_info {callconv cc; VARTYPE VTRETURN; Short NPARAMS; VARTYPE PVARTYPES [_TL_MAX_VARTYPES];

parameter:

CC

Our event response function is contemplated, this parameter must be cc_stdcall, indicating that __stdcall method

vtreturn

Return value type of the event response function

NParams

Number of parameters

Pvartypes

Corresponding parameter type, press from left to right

After understanding these, we can fill in the _TL_FUNC_INFO structure of the Downloadbegin event:

_ATL_FUNC_INFO DOWNLOADINFO = {CC_STDCALL, VT_EMPTY, 0};

Now, return to the event response chain, we add a sink_entry_info macro for each of the events we want to handle, and below is the macro to process the Downloadbegin event:

Class CMAINDLG: Public ... {... begin_sink_map (cmaindlg) SINK_ENTRY_INFO (37, DIID_DWEBBBROWSEREVENTS2, DISPID_DWEBBROWSEREVENTS2, DISPID_DWEBBESEREVENTS2, DISPID_DOWNLOADBEGIN, ONDOWNLOADBEGIN, & DOWNLOADINFO) End_sink_map ()};

The parameter of this macro is the id (37, the same as the ID of the ID of the IdispeventsImpleImpl in the inheritance list), the IID of the event interface, the Dispatch ID of the event (can be found in the MSDN or EXDISPID.H header), events Processing the name of the function and the pointer to the _ATL_FUNC_INFO structure that describes this event processing.

Write an event handler

Ok, wait for such a long time (blow a whistle!), We can write an event handler:

Void __stdcall cmaindlg :: OndownloadBegin () {// show "please wait" Here ...}

Now let's take a complicated 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, the parameters can be found for the type VARIANT from it in the end MSDN What type of data is passed, we are interested in the URL, which is a string of a BSTR type.

Describe the _ATL_FUNC_INFO structure of the befornaviGate2 event is like this:

_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 in front, the return value type is VT_EMPTY indicates that there is no return value, and NPARAMS is 7, indicating that there is seven parameters. Then the parameter type array, which is described above, for example, VT_Dispatch represents iDispatch *.

The entrance to the event response chain is very similar to the previous 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 handler is 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 wherever you'd like ...}

I bet you now get more and more like ClassWizard, because when you insert an ActiveX control to the MFC's dialog box, ClassWizard automatically completed all your work.

Convert CMAINDLG to objects. You need to pay attention to a few things. First, you must modify the global function run (). Now CMAINDLG is a COM object, we must create ccomobject to create CMAINDLG:

int Run (LPTSTR / * lpstrCmdLine * / = NULL, int nCmdShow = SW_SHOWDEFAULT) {CMessageLoop theLoop; _Module.AddMessageLoop (& theLoop); CComObject dlgMain; dlgMain.AddRef (); if (dlgMain.Create (NULL) == NULL) {Atltrace ("Main Dialog Creation Failed! / N)))); return 0;} DLGMain.ShowWindow (ncmdshow); int nret = theloop.run (); _Module.removeMessageloop (); return nret;} An alternative method is not using CComObject, and uses the CComobjectStack class, and deletes DLGMain.addRef (), CComobjectStack's implementation of the three methods of iUnknown is slightly insignificant (they are just returning from the function) because they are not Required - such COM objects can ignore counts of references because they are just a temporary object that creates in the stack.

Of course, this is not a perfect solution, ccomobjectstack is used for short-lived temporary objects, unfortunately, as long as any of the iUnknown methods that call it will trigger an assertion error. Because the CMAINDLG object calls AddRef when listening to the event, CComObjectStack does not apply to this situation.

Solving this question Either insisted on using CComobject, or derive a CComObjectStack2 class from CComObjectStack, allowing the iUnknow method to be called. The unnecessary reference count of CComobject is not a matter - people will not notice its happening - but if you have to save that CPU clock cycle, you can use the CComobjectStack2 class in this chapter in the example engineering code.

Retrospective example

Now we have seen the event response, let's take a look at the complete IEHOSTER project, which contains a browser control and responded to 6 events, and it also shows an event list, you will use them for your browser how to use them There is a sense of understanding with the interface of the progress bar, and the program has processed the following events:

BeForenaVigate2 and NaviGateComplete2: These events allow programs to control the Navigation of the URL, if you respond to the BeforenaviGate2 event, you can cancel navigation in the event's handler. Downloadbegin and DownloadComplete: Programs use these events to control the "wait" message, which means the browser is working. A more beautiful program will use an animation during this period like IE. CommandStateChange: This event tells the procedure to forward and back navigation commands when the application will change the corresponding button or unavailable. STATUSTEXTCHANGE: This event is triggered in several cases, such as mouse moving to a hyperlink. This event sends a string, the application responds to this event, displaying this string on the static control under the browser window.

The program has four buttons to control the browser: backward, stop, and refresh, call IWebBrowser2, respectively.

The data sent by the event and accompanying events is recorded in the list control, you can see the trigger of the event, you can also close some event records and just observe one of them. In order to demonstrate an important role of event processing, we check the URL in the BeforenaviGate2 event handler, if you find "DoubleClick.net", cancel the navigation. Some IE plugins such as advertisements and pop-up filters are this method rather than an 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 happensing. For example, to stop // navigates to evil tracking companies like doubleclick.net: if (SURL.FIND (_T ("DoubleClick.Net")> 0 ) * Cancel = variant_true;}

Below is the way our program works:

IEHOSTER also uses the previous chapter introduction class: cBitMapButton, CLISTVIEWCTRL (for event record), DDX (status of checkbox) and CDialogResize.

Create an ActiveX control at runtime

You can use the resource editor and you can create an ActiveX control in running. The About dialog exhibits this technology. The dialog editor premises a Group Box for the location of the browser control:

In the OnInitDialog () function we created a new Atlaxwin using CaxWind, which positioned in our pre-placed Group Box (this group box was then 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 do not 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 create an ActiveX control with the caxwindow method, there are two ways to choose: createControl () and CreateControlex (). CREATECONTROLEX () Returns the 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 of the string. The fourth parameter is a pointer of an iUnknown * type. This pointer points to the ActiveX control. IUNKNOWN interface. After creating the control, you can query the iWebBrowser2 interface, and then control it to a URL like the front. CComPtr punkCtrl; CComQIPtr 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 the ActiveX control with Progid, progid can be passed to createControlex (), instead of GUID. For example, we can create browser controls like this:

// Use the control's Progid: Create Shell.Explorer: Wndie.createControlex (l "shell.explorer", null, null, & punkctrl;

CreateControl () and CreateControlex () also have some overloaded functions for some special cases of using the browser. If your application uses the web page as an HTML resource, you can use the resource ID as the first parameter, ATL will create browsing Merry control and navigate to this resource. IEHOSTER contains a web page resource for idR_aboutpage, and we use these code in the About dialog box:

Wndie.createControl (iDR_AboutPage);

This is the display result:

Example code is used on the three methods mentioned above, you can view annotations and undeiled code in caboutdlg :: OnItDialog (), see how they work.

Keyboard event handling

The last but very important detail is the keyboard message. The keyboard processing of the ActiveX control is very complex because the controls and its host programs must work together to ensure that the control can see what it is interested in. For example, the browser control allows you to switch between links using the TAB key. MFC handles all work, so you will never realize how much workload needs to make the keyboard perfect and correct work.

Unfortunately, the wizard does not generate a keyboard processing code for a dialog-based program, of course, if you use the Form View as an SDI program of the view class, you will see the necessary code has been added to PretranslateMessage (). When the program gets the mouse or keyboard message from the message queue, use the ATL's WM_FORWARDMSG message to pass this message to the current ownership control. They usually don't do anything, but if it is an ActiveX control, the WM_FORWARDMSG message is eventually sent to the atlaxwin tolerant of this control, atlaxwin identifies the WM_FORWARDMSG message and takes the necessary steps to see if the control needs to process this message. If you have a focus window, PretranslateMessage () will then call the isDialogMessage () function, so that the navigation keys of the standard dialogs like Tab can work properly.

The PretranslateMessage () function of the example project contains these required code, because PretranslateMessage () is only valid in the Mode dialog, so if you want to use the keyboard correctly in the dialog-based application, you must use the Mode dialog box.

carry on

In the next chapter, we will return to the frame window and describe how to use a separated window.

Modify record

May 20, 2003: The article was first released.

转载请注明原文地址:https://www.9cbs.com/read-95904.html

New Post(0)