Todd Daniell, Brian Daigle, Doug Bahr, and Dave Mims
Implementing a Web View Namespace Extension Using Active Directory Services
This article assumes youre familiar with namespace extensions, C++, COM.|
Code for this article: Aug98ADSIView.exe (193KB)
Todd Daniell works in the Internet and Systems Securities Lab at Hewlett Packard. Brian Daigle, Doug Bahr, and Dave Mims are members of the DocuPACT development team at InterTech Information Management, Inc. Send questions
about this article to firstname.lastname@example.org.
Have you ever
wondered how to implement a Web View in a namespace extension? We did, too. We'll show you how to create a namespace extension using the Active Template Libraries (ATL) to implement a Web View. Our sample presents the Active Directory Services as a namespace using the
Active Directory Services Interface (ADSI). But only a minimal knowledge of ADSI is required to follow the discussion. In addition, we will demonstrate how to enable customization of your Web View using a template Web page.
The sample code included here is designed to run on Windows® 95 with Internet Explorer 4.01 (or greater), Windows NT® 4.0 Service Pack 3 with Internet Explorer 4.01 (or greater), Windows 98, or Windows NT 5.0 beta 1. We built the sample code using Visual C++ 5.0 (Service Pack 3), the Platform SDK, the ADSI SDK, and the Internet Client SDK. The Visual C++® 5.0 Service Pack can be downloaded from http://www.microsoft.com/visualc. The Platform SDK, the ADSI SDK, and the Internet Client SDK can be downloaded from http://www.microsoft.com/msdn. Before running the sample, you must install the ADSI release 2.0 runtime environment, which can be found at http://backoffice.microsoft.com/downtrial/moreinfo/adsi2.asp.
Namespace Extension Basics
A namespace extension is a component that allows Windows Explorer to browse objects that your software provides. It must be implemented as an OLE inproc server executed within the apartment model. In Windows Explorer (see Figure 1) the namespace extension is responsible for the view of the selected folder, which is typically a list view common control. The namespace extension also manages Windows Explorer's access to hierarchical objects that are exposed by the operating system or third-party programs. Typically, the type of objects that are exposed to
Windows Explorer includes storage devices, printers, and network resources. For more information about namespace extensions, refer to David Campbell's article, "Extending the Windows Explorer with Name Space Extensions" (MSJ, July 1996).
Now let's go over the different flavors of the shell you can encounter. Figure 2 outlines the different versions of the Windows Explorer shell, the platform on which they are found, and the shell COM interfaces supported. When creating a shell namespace extension, remember to define the _WIN32_IE to match the shell version that you are targeting. This will enable the interfaces defined in shlobj.h that are relevant for the specified shell version. In our sample, we targeted shell version 4.72, so we defined _WIN32_IE to 0x0401.
The code in Figure 3 shows you how to retrieve the major and minor version numbers of the Windows Explorer shell.
We decided to use ATL to implement the namespace extension because it is designed to make the development of COM servers easier. We also did not want the additional resource consumption of MFC.
When implementing a namespace extension, you must support the following
interfaces: IShellFolder, IPersistFolder, IShellView, and IEnumIDList. To generate
the Web View, you must also support IPersistFolder2 and IShellView2. (Note: IPersistFolder2 is only available on shell versions 4.72 and greater.) The implementation of these interfaces is what constitutes the core namespace code.
When generating a namespace extension project using ATL, you must group the IShellFolder, IPersistFolder, and IPersistFolder2 interfaces into a single ATL class. Also, you must group IShellView and IShellView2 into another ATL class. Explorer requires these interfaces to be supported by a single object. An example of this can be seen in the classes CADSIShellFolder and CADSIShellView.
A problem we encountered when creating the namespace extension was implementing the registration code using the ATL Registry Scripts. The problem was due to the lack of support in ATL Registry Scripts for binary types. Also, the RISC version of the Visual C++ 5.0 compiler generates code that will stack fault when writing to HKEY_LOCAL_
MACHINE registry entries from the ATL Registry Scripts. To work around this problem, we overloaded the CADSIShellFolder::UpdateRegistry ATL method. We called the standard _Module.UpdateRegistryFromResource to complete the bulk of the processing using the standard ATL Registry Scripts.
Using the IShellFolder Interface
The IShellFolder interface is the primary interface that connects all of the other objects and information to the Windows Explorer. In using IShellFolder, Explorer binds to, compares, gets information, sets information, and acquires UI for other objects in the namespace extension. The first IShellFolder object that is created by Explorer is the root of the namespace extension.
Windows Explorer gets things started by doing a CoCreateInstance on the CLSID of your class and IID of
IID_IShellFolder. Next, it will QueryInterface for IPersistFolder so it can initialize the IShellFolder object. IPersistFolder::Initialize is called with the location where the folder is rooted.
The most important element to consider when designing your IShellFolder object has nothing to do with the folder itself. Rather, it has to do with how the folder will represent its children to Windows Explorer. This representation is done through a buffer of data called a shell item ID. A group of IDs form an IDList. A pointer to an IDList is called a PIDL (pronounced piddle).
Designing the PIDL
Windows Explorer perceives a shell item ID as a magic cookie. The only field that Explorer recognizes in the ID is the first two bytes. These two bytes must be an unsigned short representing the size of the data that is contained within the ID. As a result, Windows Explorer can concatenate and enumerate through PIDLs without knowing the structure of the data in each ID. An ID of 0 length signifies the end of a PIDL. A simple (or relative) PIDL is one that contains information about a single object, much the same way that a relative file name would be used. A complex (or absolute) PIDL has a shell ID for every node in the hierarchy path of the object. A PIDL is analogous to a NULL terminated file name (see Figure 4). A file name can be relative to its parent or a fully qualified path.
| Figure 4 Types of PIDLs|
Another requirement that Windows Explorer places on a PIDL is that it must be allocated and freed using the IMalloc object returned by SHGetMalloc. This is because the ID is just a buffer of data, not an object. There are no reference counts or interfaces on a PIDL. As a result, when you allocate an ID and hand it back to Explorer, Explorer must be able to free the PIDL.
Every object within your namespace must have a unique absolute PIDL that is persistable. The object must be able to be regenerated from the absolute PIDL when requested by the Explorer. This means that you cannot have pointers to other objects in your ID, and it must contain all of the information needed to return to your original object. In the ADSI ID we chose to include the complete "ADSI" path to the object.
Because PIDLs can be complex, you need to be able to ensure that the ID you are trying to manipulate is yours. For this reason, it is strongly suggested that a signature field follow the size field in the ID. We chose to have a signature field of an ANSI buffer of "ADSI." Because IDs are persistable, and to ensure backwards compatibility, it is a good idea to have a version stamp immediately following the signature. Because we weren't greatly concerned with backward compatibility, we chose to forego a version stamp in our sample.
When Windows Explorer needs to know the title of an object, it calls IShellFolder::GetDisplayNameOf. The structure STRRET (see Figure 5) that is also passed in must be filled out with how you are supplying the information. The fastest and easiest way to supply the display name is to tell Windows Explorer that it is an ANSI string inside of the PIDL. To do this, set the uType field of the STRRET structure to STRRET_OFFSET and the uOffset field to the number of BYTES from the start of the PIDL to the string, as shown in the following code:
pIDL,DWORD /*uFlags*/, LPSTRRET lpName)
// get the ID Info
ADSI_IDLIST * pIDInfo = GetPidlInfo(pIDL);
// set that we are returning this as an offset in
// the IDList
lpName->uType = STRRET_OFFSET;
// figure the offset
lpName->uOffset = ((LPBYTE)pIDInfo->Title.szString
// return all is well
You cannot assume that the PIDL passed in is a simple PIDL. If it is a complex PIDL, you must be able to traverse the lists of IDs and compute the offset from the start of the PIDL to your title.
Some methods are currently documented as taking only simple PIDLs and some are documented as taking both. If you assume that all methods take complex PIDLs and write a routine to run the PIDL that returns the last valid ID, then you do not have to worry about which methods take simple or complex PIDLs. By simply assuming that all methods take a complex PIDL, you should have future compatibility with any changes that Microsoft might implement.
Another optimization that could be contained within the ID is the attributes that Windows Explorer wants on the object. Explorer calls IShellFolder::GetAttributesOf to obtain this information. These attributes disclose things like whether the object is a folder, has children, and can be moved, copied, renamed, deleted, and so on. Figure 6 lists the most common attributes of an object. Because this information usually does not change, it is a good idea to store it within the ID.
Implementing the strings inside the ID posed a problem. Both the display name and ADSI path are variable in length, so they could not be defined inside the ID structure. This made implementing the ID a little more challenging. To overcome the problem, the ADSI ID is not a single structure, but rather two structures.
When Windows Explorer needs to open another level in the tree control it will call two functions. First it must enumerate the list of children from the namespace. To do this, it will call IShellFolder::EnumObjects passing in flags that indicate the types of objects it wants. This method returns an enumerator object called IEnumIDList that list the PIDLs for the children of the folder.
To actually get the object represented by the PIDL, Windows Explorer will call IShellFolder::BindToObject. You will need to make sure that the PIDL represents an object with the interface that Explorer is requesting. Generally, this method will be called when Explorer needs another IShellFolder object representing a sublevel of folders for the tree.
Setting Up the User Interface
Finally, to start displaying the objects returned in the PIDLs, Windows Explorer would call one of two methods. The first method, IShellFolder::GetUIObjectOf, is called to obtain the menus, drop targets, data objects, and icons for the selected objects in the array of PIDLs. You must handle the multiple selection issues in GetUIObjectOf. If you need to present different context menus on multiselected objects, this is where you handle it. Generally, calls for icons are passed in one at a time. Currently, the valid IIDs that Windows Explorer asks for are IID_IDataObject, IID_IDropTarget, IID_IContextMenu, and IID_IExtractIcon. However, to be forward compatible we recommend that you check for each interface and not assume a default interface. Instead, return E_NOTIMPL on any unsupported interfaces.
Of the four interfaces mentioned above, we only had to implement IExtractIcon. Our sample does not support dragging and dropping, so IDataObject and IDropTarget are not needed. Context menus were also not implemented in our namespace extension. For these three interfaces we simply return E_NOTIMPL.
Since we did not want the standard unknown icon from Windows Explorer, we had no choice but to implement the IExtractIcon interface. Actually, IExtractIcon is not really the correct name. Depending on how you are compiling your namespace, IID_IExtractIcon and IExtractIcon are defined to really be IID_IExtractIconA and IExtractIconA or IID_
IExtractIconW and IExtractIconW respectively. Windows NT will first ask for the Unicode version, and if it's not found will then look for the ANSI version. Windows 95 will only ask for the ANSI version. Thus, if you only want to support one version, implement the ANSI version. On the other hand, if you want the fastest speed on Windows NT, you need to implement both interfaces. This can be done on the same object or on separate objects. In our sample extension, we chose to implement both interfaces as separate objects. The only difference between the two forms is whether the file name is returned in an ANSI string or a Unicode string.
It's very simple to implement IExtractIcon when all you want to do is tell Windows Explorer to use icons from your DLL. First, in the method IExtractIcon::GetIconLocation (see Figure 7), return the complete path to the module where the icon is found. Then, take the resource ID of the icon, set the high-order bit, and place the value in the *piIndex parameter. In the IExtractIcon::Extract method return S_FALSE. Do not return E_NOTIMPL because Explorer looks for the S_FALSE return code from IExtractIcon::Extract to load the icon.
The most important difference between Windows NT and Windows 9x with regard to namespace extensions is the support of Unicode. Window NT generally wants all of its strings in Unicode and prefers all of its objects to support the Unicode strings. If you have a problem with strings only on one platform in Windows Explorer, suspect Unicode/ANSI problems first. You might even detect which OS you're running on and then supply the correct string to Windows Explorer.
Implementing the Classic View
Now let's discuss the view of the selected folder. Even though the view can be whatever you dream up, typically it is presented as a list view (see Figure 8). This is because a list view provides consistent presentation to the user across namespace extensions. Internet Explorer 4.0 has extended this by introducing the Web View. From the user's standpoint, the most noticeable change between a list view and a Web View is that the Web View offers more information about selected items as demonstrated in the sample. Typically, the Web View is created from a template. This allows users or integrators that are familiar with DHTML to add additional functionality to the namespace by modifying the template. From our standpoint, this feature is something new and useful that should be supported to provide a seamless integration with newer versions of Explorer.
When tackling the shell view, you must initially address issues surrounding the presentation of folder views. Our default view is the classic presentation of a list view. To provide this support using ATL requires the following. First, add comctl32.lib to the link tab under Project Settings and include commctrl.h in stdafx.h. Second, use ATL's window-based objects and message map support. To set this up, be sure to include atlwin.h in stdafx.h and make sure to include atlwin.cpp in stdafx.cpp. Including atlwin.cpp will help overcome linker errors with unresolved externals (Lnk 2001) for ATL::CWindowImplBase::Create, ATL::
CWindowImplBase::Register, and ATL::CWindowImplBase::StartWindowProc. This is also mentioned at msnews.microsoft.com in the microsoft.public.vc.activextemplatelib newsgroup.
Another issue you must address is the lack of message reflection in ATL. Message reflection is a mechanism that keeps controls more self-contained by allowing messages to be handled by the control itself. MFC accomplishes this with OCXs by sliding a window in between the control and the container to reflect messages back to the control itself.
The implementation of the list view for our sample namespace extension's Classic View is found in the CListView32 class. The CListView32 is a window that also has a member variable of a CContainedWindow object that superclasses the list view (see Figure 9). Instead of having the CListView32 window reflect messages to the list view, we are taking advantage of an ATL implementation that lets the two windows share the same message map. An ATL message map is really just a virtual inline WinProc called ProcessWindowMessage. Because CListView32 is registered with the CContainedWindow on the constructor, the list view sends messages back to the CListView32 window's message map by calling the CListView32's ProcessWindowMessage from its own WinProc.
Figure 9 Classic View
The functionality of the list view is exposed from the CListView32 API. The API is somewhat grid centric by exposing row and column-based methods. There is also basic support for image lists. As icons are retrieved from the associated folder, they are inserted into the image list of the list view. If the icon has already been cached, its associated image index is used instead of duplicating the icon in the image list.
So that the CListView32 can be reusable, an optional callback class called CListView32Events has been implemented. This allows CListView32 to be unspecific to our shell implementation. If an object inherits from CListView32Events and is registered on the constructor of a CListView32 object, the CListView32Events-derived object will receive events from the CListView32 object. This is what was done with the Classic View. CADSIClassicView inherits from CListView32Events and registers itself with its member CListView32 object, so that it can receive events from the CListView32 object.
Now that we have covered CListView32, let's move on to the shell view interfaces. We will implement only the basic and required methods of the shell view interfaces for our sample. To accomplish this we have to associate a shell view with its shell folder, create the view, provide the view window, be able to refresh the view, track and supply the associated folder settings, and destroy the view. In addition, we will need to have support for inserting menu items and toolbars into Windows Explorer.
Let's examine the relationships of the various view classes. The implementation of IShellView and IShellView2 are in CADSIShellView. CADSIShellView creates CADSIDefView, which is called our Default View. The Default View supplies the window handle that is returned to Windows Explorer. It also propagates interface calls to either the Classic View or the Web View. Next, we will discuss the Classic View implementation that namespace extensions currently support (see Figure 10). Later when we cover the Web View, we will discuss the necessity of the Default View. To simplify issues, the Classic View will be presented as if it were called directly from the shell view interface.
The shell view is associated with a shell folder via IShellFolder::CreateViewObject. The call supplies a window to use as the parent for any error message boxes. Microsoft documents that this window may be different from the window supplied when the view is actually created by calling IShellView::CreateViewWindow. Also, each CreateViewObject request should create a new instance of a shell view object. The IID of the requested shell view interface is also supplied. In our implementation, the ADSI shell view is created and initialized with the ADSI shell folder before returning ownership of the view to the caller. The shell view needs the ADSI folder to populate itself with values from properties that are consistent across all ADSI objects.
Figure 10 Classic Layout
The implementation of our shell view, called CADSIShellView, does not inherit from CComCoClass nor does it have an entry in the ATL BEGIN_OBJECT_MAP. This is because it is not cocreatable. Instead, it is only instantiated from a call to IShellFolder::CreateViewObject. But the Classic View that ultimately handles calls from CADSIShellView is cocreatable. As a result, CADSIClassicView inherits from CComCoClass and has an entry in the ATL BEGIN_OBJECT_MAP. This is necessary so the view can be cocreated from the DHTML page during the Web View. The Classic View will no longer be requested solely from IShellFolder::CreateViewObject. We'll discuss this in more detail later.
The CComCreator and CComObject templates instantiate the shell view to return from CADSIShellFolder::CreateViewObject. This is the same mechanism that ATL uses when handling requests from COM clients to cocreate an object. If an attempt to use "new" to instantiate the custom shell view interface had been made instead,
it would result in "Cannot instantiate abstract class (error C2259) due to the IUnknown interface methods." The CComObject template implements the IUnknown interface methods and propagates those calls on to internal implementations provided by the CComObjectRoot template. The Classic View interface inherits from the CComObjectRoot template. The CComCreator template is a utility that instantiates and initializes the COM object, then queries the object for the specified interface to return.
The list view is created via the call to IShellView::CreateViewWindow. The call supplies the previous shell view object. Microsoft documents that this parameter may be NULL, so never assume that it is valid. It provides optimized browsing between similar views. We did not take advantage of that in our sample. The folder settings specify attributes of how to create the view window and we used it in creating our view. Folder settings are also cached for calls made to IShellView::GetCurrentInfo. We tracked our own reference to the shell browser so we can communicate with Windows Explorer (don't forget about the rules regarding in and out parameters). An example of this will be discussed when we use the shell browser object to browse into a folder of the list view. The final two parameters denote the dimensions for the shell view and an out parameter that is updated with the window handle of our view.
Before the view window is actually created, we must obtain the window handle associated with the shell browser object. This will be used as the parent window. Then we determine how the view should be created based on the shell folder settings. Finally, we initialize and set the list view with data based on the ADSI folder selected in the tree view. The ADSI folder is the parent folder of the shell view that was supplied when the view was created in IShellFolder::CreateViewObject. Once the ADSI COM object associated with the shell folder is obtained by supplying our PIDL helper with its associated ADSI path, the sub ADSI objects of the shell folder are enumerated and inserted into the list view. The list view displays the consistent properties that are valid for all ADSI objects. These include name, path, guid, class, schema, and parent path. Note that depending on the ADSI object other unique properties can exist and be queried based on the ADSI schema.
Just like the My Computer namespace extension, our sample can browse into a folder when a user double clicks a folder in the list view. The folder is then selected in the tree view and its children are displayed in the list view. As we mentioned before, the ADSI shell view has been registered with its list view to receive control events. Of particular interest here is the NM_DBLCLK notification that is handled by CADSIClassicView::OnDblClick. We first get the ADSI path associated with the selected item from the list view. Remember, the path is one of the attributes consistent across all ADSI objects and is displayed in the list view. Then, we use our ADSI PIDL helper to build a PIDL based on the path. If it is not a container of other ADSI objects, the action is ignored. Otherwise, we use our cached shell browser object and invoke its BrowseObject method with the PIDL and the default browser flag. Lastly, we clean up our PIDL.
Two other methods that need to be implemented are IShellView::GetWindow and IShellView::Refresh. These interface methods will be called when the user requests a refresh of the view by selecting the F5 key. We should return the window handle of the list view from the call to GetWindow. For Refresh, we clear the data in the list view control and retrieve the values again from the ADSI objects as we did when the shell view was first created.
The shell view returns the current folder settings associated with the list view from calls to IShellView::GetCurrentInfo. Remember that the initial folder settings were supplied in the call to IShellView::CreateViewWindow. GetCurrentInfo used along with CreateWindowView allows the Windows Explorer to maintain a somewhat consistent view of data across different namespace extensions. Implementation of this method is required, not because of Explorer, but because of problems seen with the My Computer namespace extension. Before this method was implemented and it returned E_NOTIMPL, the My Computer namespace extension would fail to create its list view or update its menus correctly when moving from the ADSI sample namespace extension to the My Computer namespace extension. After the view of the My Computer namespace extension would fail to create and we moved back to the ADSI sample namespace, the view mode for the folder settings of the previous view supplied in CreateWindowView would have a value of zero, which is not a valid value. Valid values should be between FVM_ICON and FVM_DETAILS.
Lastly, we must close and clean up any resources we have associated with the view. This is expected when IShellView::
DestroyViewWindow is invoked. In the DestroyViewWindow method, we flush the list view and destroy all associated windows. When the shell view is finally destroyed, it will release its ownership to the shell browser.
Implementing the Web View
Now that we had a bare bones shell view running and communicating with ADSI, the next step was to provide the Web View. Following the Microsoft® model, the Web View is the Classic View displayed from within a DHTML page and is driven by the IShellView2 interface. If the IShellView2 interface exists, Windows Explorer will use it instead of the older IShellView interface. The IShellView2 interface extends the IShellView interface with four new methods: GetView, CreateViewWindow2, HandleRename, and SelectAndPositionItem. Windows Explorer calls the GetView function for the Default view, the Current view, and the list of views supported. Each view is represented as a GUID for uniqueness throughout the system called a shell view ID. Windows Explorer then uses the shell view ID when calling CreateViewWindow2, which is responsible for creating the appropriate view. When we first designed CreateView methods, we branched and then created either a Classic View object or a Web View object. The containing window for the view would be the view itself. The problem was that Explorer expected the window handle that was returned from the CreateViewWindow2 to remain unchanged. Specifically, the size messages for the view were getting lost after switching views. The new view would not resize within the right pane of Windows Explorer. The solution required creating the Default View, another layer between the view objects and the shell's view. Earlier when using Spy++, we noticed that Microsoft had a containing window called SHELLDLL_DefView between Windows Explorer and the WebBrowser windows. This supports our finding as to why multiple views must have a containing window of any represented view.
The Default View was implemented using the basic set of methods needed from IShellView2 to drive the individual views. The CADSIDefView::CreateView is the launching pad for any view and is called from IShellView2::CreateWindowView2. Since the shell view defaults to the Classic View, the sample will always start in the Classic View mode. The current view could be saved by persisting the shell view ID. The GetView call would then return the same shell view ID for the Default View. The ShellView2 creates a CADSIDefView object and calls Create View to create the Default View with the shell view ID. The Default View then creates a containing window and a view object based upon the shell view ID. In our sample, the shell view ID is the CLSID for the COM object that represents the shell view. The creation of the individual views is the same as the creation of the Default View. Other methods are implemented in the Default View to propagate calls from IShellView2 to each individual view object. These methods allow refreshes when the F5 key is pressed and when folder settings change. This also allows the Web View to have the four older classic viewing modes (large icon, small icon, list, and details) combined with the view settings. The view modes act as a multiplier for the total number of possible views. We have two IShellView2 views that can have four viewing modes. As a result, the user has eight different representations of the same data. The Web View was created to reuse as much code as possible from the existing namespace extensions. The Web View contains an instance of the Classic View displayed from within a DHTML page. This allows all of the older Classic View shell code to still be used, but it is now used as an ActiveX
control within a Web page.
During the creation of the Web View, it is necessary to host a browser within our Default View window. There are two approaches to hosting the Web View. One approach is using the interfaces that are exposed by MSHTML.DLL. The other is to create an OLE container and insert the ActiveX WebBrowser Control into that container. We have chosen to use the latter because it is easier to implement. We created the CADSIWebView object as a simple container. In the CADSIWebView::CreateView, we created our container's host window. Then we created an instance of the WebBrowser control and activated it in-place within our host container. Once we created the WebBrowser control, the next step was to have it load the DHTML page.
We queried for the IWebBrowser2 interface from the IOleObject pointer that we got when creating the WebBrowser control. This allowed direct communication with the WebBrowser control by using its dual interface. One of the very few calls we made to the WebBrowser control was the Navigate2 method, which is used to load a Web page into the WebBrowser control.
The DHTML Templates (XXX.HTT) are the files that get loaded by the WebBrowser control. These files are specific to each namespace extension design. For our sample, we load the template from the same directory where the namespace extension is currently registered. Therefore, for simplicity's sake the ADSI.HTT template file and the ADSI.GIF file must be in the same directory as our namespace extension. Microsoft uses a separate template for each namespace extension. Each template's location is persisted under the corresponding namespace extension's CLSID section. Choose whichever method is more practical for your own namespace extension.
Now that we have loaded our Web page into the WebBrowser control, we must determine how to get our Classic View to be a part of the Web View DHTML document. The Classic View is placed within the DHTML document as an ActiveX control. Microsoft provides a similar implementation from their DHTML templates. We verified their implementation by customizing a folder for the C: drive. You can see this by selecting the Customize this Folder option under the My Computer namespace extension in Windows Explorer. A dialog will ask if you would like to edit the HTML script. After answering a few questions, you can edit the Web View template to be virtually anything you desire. At the bottom of the template script, you will find an object being declared in the script, Microsoft's SHELLDLL_DefView object that's used to create their classic view. Microsoft's default view object is located in SHDOCVW.DLL. In our sample, the ActiveX control we insert into the DHTML script is the Classic View object. We have learned that the Web View hosts the WebBrowser control and WebBrowser hosts our Classic View (see Figure 11).
Figure 11 Web View Layout
The most difficult obstacle in researching the Web View was figuring out how the Web View and the Classic View would communicate. The key was understanding the design of DHTML and the Document Object Model (DOM). The DOM is defined by groups of collections. There are different types of collections contained within each DHTML document, and each collection type has a different set of information that can be obtained from the document or changed in the document. There are a large number
of interfaces that allow access to DHTML DOM. These interfaces are typically named "IHTMLxxxx." They are grouped based upon the collection and element types within the document. All of the interfaces are located in the MSHTML.DLL and are described in the Internet Client SDK documentation.
The key is understanding that the DHTML document can be accessed from the IWebBrowser2 interface. As a result, the host container of the WebBrowser control, the scriptlets within the HTML page, and the ActiveX controls loaded within the page have access to the DHTML DOM interfaces using COM. By enumerating the elements within the document, our code can find an element of the OBJECT tag type. Then it examines the element using the IHTMLElement interface and gets the IHTMLObjectElement interface. From the IHTMLObjectElement interface, we compare the CLSID of the object to make sure it is the same as the Classic View object's CLSID. We can then get an IDispatch pointer to the Classic View instance. Now we have the communication necessary to complete the creation of the Classic View within the Web View. Whew!
Just after calling the Navigate2 method to instruct the WebBrowser control to load our Web page, we walk the DHTML DOM to locate the Classic View object. A problem that we ran into was that the Classic View control may not be loaded when the WebBrowser control returns from the Navigate2 call. Avoiding this would require knowing when the WebBrowser control has completed document loading before we actually create the Classic View for a specified folder location. We later determined that WebBrowser supports calling a connection point to receive events. This required the Web View to support the DWebBrowserEvents2 dual interface. The events interface notifies the sink when events occur. We are primarily concerned when the document has been fully loaded. The DocumentComplete event is received for each document that is loaded. Because the DHTML DOM allows documents within documents, we must wait for the outer document to completely load. We know the entire document is loaded when the DocumentComplete event has the IWebBrowser2 interface equal to the IDispatch pointer of the document. One of the parameters of the DocumentComplete event is the IDispatch pointer of the completed document. Now that we have loaded the DHTML document, we can examine all of the elements in the document for our Classic View object.
The connection is made to the Classic View by calling the CreateView method. The information necessary to create the list view within the currently empty Classic View control is passed to the CreateView method. We also wanted to search and replace some of the text within the DHTML script so that the current path is displayed on the Web page. Searching for the text is similar to getting the Classic View IDispatch pointer, with one exception. When we examine every element, we look at the inner text of the DHTML script and parse it for our ADSI path parameter called %THISADSIPATH%. We replace the parameter text with our ADSI path that we get from the created Classic View. Then, we place the updated text back into the DHTML document. Finally, the Web View is visible with the Classic View fully initialized and accessible to the user.
Toolbars and Menus
The final piece to completing the Web View is having the Classic View communicate with the Java scriptlet within our DHTML template. When an item is selected in the list view, it must fire an event into the scriptlet. The scriptlet can then determine what to do during a selection. In our sample, the scriptlet calls back into the Classic View object and requests information to be displayed in the left portion of the Web View. Our sample demonstrates how to pass parameters between the ActiveX control and the Java scriptlet. Your namespace extension can pass any event or any information between the control and the scriptlet code. Also, you can have multiple ActiveX controls that communicate using the DHTML DOM to other controls, other scriptlets, and the host container for the WebBrowser control.
Toolbars and menus are initialized by the IShellView2::
UIActivate method. In the sample, this functionality is handled in the class CADSIDefView. The shell's view has several varying states of activation (see Figure 12 ). Within the UIActivate method, the view should not change focus, and it should not hook the WM_KILLFOCUS message to re-merge menu items. However, the view should hook the WM_SETFOCUS message and re-merge the menu items after calling IShellBrowser::OnViewWindowActivated.
Our sample implements view menu items and toolbars that allow the namespace extension to change between view modes. In addition, we support the As Web Page option under the View menu. The purpose of the As Web Page menu option is to toggle the current view between Classic View and Web View. This process takes place in the CADSIDefView class. The view is switched by destroying the current view, changing the current shell view ID, and creating a new view.
When we first started implementing the menus and the toolbars, we put them into the Classic View. This became a problem when we tried switching views, because the Classic View was destroyed by the Shell Browser when the As Web Page menu option from the view menu was selected. This would cause the system to fault when the Shell Browser would return to the Classic View, which no longer existed in memory. After we moved the toolbars and menus into our Default View, we were able to successfully destroy the current view and create the requested view.
Looking back, we have learned a tremendous amount of information about some very cool and exciting technologies. As you can see, implementing the Web View in a namespace extension is not a trivial task. It requires a detailed knowledge of many technologies. A good namespace extension offers a seamless integration with the operating system's shell. Beyond that, you are limited only by your imagination.
We would like to extend special thanks to our fellow members of the DocuPACT development team for their support.
From the August 1998 issue of Microsoft Systems Journal.
Get it at your local newsstand, or better yet, subscribe.