|
|||||||||||||||||||||
|
|||||||||||||||||||||
|
Announcements
Chapters
Services
Feature Zones
|
Contents
IntroductionHere in Part VI, I'll cover ATL's support for hosting ActiveX controls in dialogs. Since ActiveX controls are ATL's specialty, there are no additional WTL classes involved. However, the ATL way of hosting is rather different from the MFC way, so this is an important topic to cover. I will cover how to host controls and sink events, and develop an application that loses no functionality compared to an MFC app written with ClassWizard. You can, naturally, use the ATL hosting support in the WTL apps that you write. The sample project for this article demonstrates how to host the IE WebBrowser control. I chose the browser control for two good reasons:
I certainly can't compete with folks who have spent tons of time writing custom browsers around the WebBrowser control. However, after reading through this article, you'll know enough to start working on a custom browser of your own. Starting with the AppWizardCreating the projectThe WTL AppWizard can create an app for us that is ready to host ActiveX controls. We'll start with a new project, this one will be called IEHost. We'll use a modeless dialog as in the last article, only this time check the Enable ActiveX Control Hosting checkbox as shown here:
Checking that box makes our main dialog derive from The generated codeIn this section, I'll cover some new pieces of code that we haven't seen before from the AppWizard. In the next section, I'll cover the ActiveX hosting classes in detail. The first file to check out is stdafx.h, which has these includes: #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 the important ones. They contain the definitions of some COM-related classes (like the smart pointer Next, look at the declaration of class CMainDlg : public CAxDialogImpl<CMainDlg>, public CUpdateUI<CMainDlg>, public CMessageFilter, public CIdleHandler
Finally, there's one new line in int WINAPI _tWinMain(...) { //... _Module.Init(NULL, hInstance); AtlAxWinInit(); int nRet = Run(lpstrCmdLine, nCmdShow); _Module.Term(); return nRet; }
Due to a change in ATL 7, you have to pass a LIBID to _Module.Init(NULL, hInstance, &LIBID_ATLLib); This change has worked fine for me. Adding Controls with the Resource EditorATL lets you add ActiveX controls to a dialog using the resource editor, just as you can in an MFC app. First, right-click in the dialog editor and select Insert ActiveX control:
VC will show a list of the controls installed on your system. Scroll down to Microsoft Web Browser and click OK to insert the control into the dialog. View the properties of the new control and set its ID to
If you compile and run the app now, you'll see the WebBrowser control in the dialog. However, it will be blank, since we haven't told it to navigate anywhere. In the next section, I'll cover the ATL classes involved in creating and hosting ActiveX controls, and then we'll see how to use those classes to communicate with the browser. ATL Classes for Control HostingWhen hosting ActiveX controls in a dialog, there are two classes that work together: CAxDialogImplThe first class is
CONTROL "",IDC_IE,"{8856F961-340A-11D0-A96B-00C04FD705A2}", WS_TABSTOP,7,7,116,85 The first parameter is the window text (an empty string), the second is the control ID, and the third is the window class name. CONTROL "{8856F961-340A-11D0-A96B-00C04FD705A2}", IDC_IE, "AtlAxWin", WS_TABSTOP, 7, 7, 116, 85 The result is that an Once AtlAxWin and CAxWindowAs mentioned above, an The Since the ActiveX control is a child of the Calling the Methods of a ControlNow that our dialog has a WebBrowser in it, we can use its COM interfaces to interact with it. The first thing we'll do is make it navigate to a new URL using its BOOL CMainDlg::OnInitDialog()
{
CAxWindow wndIE = GetDlgItem(IDC_IE);
Next, we declare an CComPtr<IWebBrowser2> pWB2; HRESULT hr; hr = wndIE.QueryControl ( &pWB2 );
if ( pWB2 ) { CComVariant v; // empty variant pWB2->Navigate ( CComBSTR("http://www.codeproject.com/"), &v, &v, &v, &v ); } Sinking Events Fired by a ControlGetting an interface from the WebBrowser is pretty simple, and it lets us communicate in one direction - to the control. There is also a lot of communication from the control, in the form of events. ATL has classes that encapsulate connection points and event sinking, so that we can receive the events fired by the browser. To use this support, we need to do four things:
The VC IDE helps out greatly in this process - it will make the changes to Adding handlers in VC 6There are two ways to get to the UI for adding handlers:
After choosing that command, VC shows a dialog with a list of controls, labeled Class or object to handle. Select IDC_IE in that list, and VC will fill in the New Windows messages/events list with the events that the WebBrowser control fires.
We'll add a handler for the DownloadBegin event, so select that event and click the Add and Edit button. VC will prompt you for the method name:
The first time you add an event handler, VC will make a few changes to #import "C:\WINNT\System32\shdocvw.dll" class CMainDlg : public CAxDialogImpl<CMainDlg>, public CUpdateUI<CMainDlg>, public CMessageFilter, public CIdleHandler, public IDispEventImpl<IDC_IE, CMainDlg> { // ... public: BEGIN_SINK_MAP(CMainDlg) SINK_ENTRY(IDC_IE, 0x6a, OnDownloadBegin) END_SINK_MAP() void __stdcall OnDownloadBegin() { // TODO : Add Code for event handler. } }; The The inheritance list now has The sink map is delimited by the Finally, there is the new method Adding handlers in VC 7There are again two ways to add an event handler. You can right-click the ActiveX control in the dialog editor and pick Add Event Handler on the menu. This brings up a dialog where you select the event name and set the name of the handler.
Clicking the Add and Edit button will add the handler, make necessary changes to The other way is to view the property page for
You can click the arrow next to an event name and pick <Add> [MethodName] on the menu to add the handler. You can modify the handler's name later by changing it right in the property page. VC 7 makes almost the same changes to Advising for eventsThe last step is to advise the control that Advising in VC 6ATL in VC 6 has a global function To use this function, add handlers for BOOL CMainDlg::OnInitDialog(...)
{
// Begin sinking events
AtlAdviseSinkMap ( this, true );
}
void CMainDlg::OnDestroy()
{
// Stop sinking events
AtlAdviseSinkMap ( this, false );
}
Advising in VC 7In VC 7, The big difference compared to VC 6 is that BEGIN_MSG_MAP(CMainDlg)
CHAIN_MSG_MAP(CAxDialogImpl<CMainDlg>)
// rest of the message map...
END_MSG_MAP()
Overview of the Sample ProjectNow that we've seen how event sinking works, let's check out the complete IEHost project. It hosts the WebBrowser control, as we've been discussing, and handles six events. It also shows a list of the events, so you can get a feel for how custom browsers can use them to provide progress UI. The program handles the following events:
The app also has four buttons for controlling the browser: back, forward, stop, and reload. These call the corresponding The events and the data accompanying the events are all logged to a list control, so you can see the events as they are fired. You can also turn off logging of any events so you can watch just one or two. To demonstrate some non-trivial event handling, the void __stdcall CMainDlg::OnBeforeNavigate2 ( IDispatch* pDisp, VARIANT* URL, VARIANT* Flags, VARIANT* TargetFrameName, VARIANT* PostData, VARIANT* Headers, VARIANT_BOOL* Cancel ) { CString 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; } Here's what the app looks like while viewing the Lounge:
IEHost demonstrates several other WTL features that have been covered in earlier articles: Creating an ActiveX Control at Run TimeIt is also possible to create an ActiveX control at run time, instead of in the resource editor. The About box demonstrates this technique. The dialog resource contains a placeholder group box that marks where the WebBrowser control will go:
In 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 use a CComPtr<IUnknown> punkCtrl; CComQIPtr<IWebBrowser2> pWB2; CComVariant v; // empty VARIANT // 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 that have ProgIDs, you can pass the ProgID to // Use the control's ProgID, Shell.Explorer: wndIE.CreateControlEx ( L"Shell.Explorer", NULL, NULL, &punkCtrl );
wndIE.CreateControl ( IDR_ABOUTPAGE ); Here's the result:
The sample project contains code for all three of the techniques described above. Check out Keyboard HandlingOne final, but very important, detail is keyboard messages. Keyboard handling with ActiveX controls is rather complicated, since the host and the control have to work together to make sure the control sees the messages it's interested in. For example, the WebBrowser control lets you move through links with the TAB key. MFC handles all this itself, so you may never have realized the amount of work required to get keyboard support perfectly right. Unfortunately, the AppWizard doesn't generate keyboard handling code for a dialog-based app. However, if you make an SDI app that uses a form view as the view window, you'll see the necessary code in If the window with the focus does not recognize The sample project contains the necessary code in Up NextIn the next article, we'll return to frame windows and cover the topic of using splitter windows. Copyright and licenseThis article is copyrighted material, (c)2003-2006 by Michael Dunn. I realize this isn't going to stop people from copying it all around the 'net, but I have to say it anyway. If you are interested in doing a translation of this article, please email me to let me know. I don't foresee denying anyone permission to do a translation, I would just like to be aware of the translation so I can post a link to it here. The demo code that accompanies this article is released to the public domain. I release it this way so that the code can benefit everyone. (I don't make the article itself public domain because having the article available only on CodeProject helps both my own visibility and the CodeProject site.) If you use the demo code in your own application, an email letting me know would be appreciated (just to satisfy my curiosity about whether folks are benefiting from my code) but is not required. Attribution in your own source code is also appreciated but not required. Revision History
Series Navigation: « Part V (Advanced Dialog UI Classes) | » Part VII (Splitter Windows) | ||||||||||||||||||||