This article may contain URLs that were valid when originally published, but now link to sites or pages that no longer exist. To maintain the flow of the article, we've left these URLs in the text, but disabled the links.


MIND



Cutting Edge
cutting@microsoft.com         Download the code (92KB)
Dino Esposito

Shell Support for Dynamic HTML

O
f all the new features in Windows® 95 and Windows NT® 4.0, the programmable shell is one of the most exciting—to programmers, anyway. Everything you see and use in the Windows 95 or Windows NT 4.0 desktop can be customized. This applies to shortcuts, icons, context menus, and the contents of folders. For example, you might want to change the way a particular folder shows its items, as the Recycle Bin or Temporary Internet Files do. This sort of customization is called a namespace extension.
      A namespace extension, though, is not the only way to extend the capabilities of your shell. Windows offers several opportunities for hooking and customizing specific system actions. A second approach to this is shell extensions. Both namespace and shell extensions are actually implemented as in-process COM servers.
      In this column, I'll start by discussing the architecture of a namespace extension and the nitty-gritty details of how to write one. Next, I'll attack the Microsoft® Internet Explorer 4.0 stuff and design a reusable C++ class that allows you to query any HTML file for its internal elements. Finally, I'll provide a shell-based tool to browse the contents of HTML documents, taking advantage of the coolest features of Dynamic HTML (DHTML) and Internet Explorer 4.0 (see Figure 1).
Figure 1: Dynamic HTML Browser
Figure 1: Dynamic HTML Browser

The Shell Namespace
      If you want Windows 95 and Windows NT 4.0 to treat your documents in a special fashion, you have one option: write some extensions to the system's shell. This basically consists of implementing a handful of COM interfaces that Windows Explorer will call back when accomplishing a given task for a given kind of document.
      You can write a namespace extension to enrich the collection of shell objects. Accessories, Recycle Bin, Control Panel, and My Briefcase are all symbols held by the shell and which form its namespace. The root of this domain is the Desktop object. Directories and files are no longer the only components of the system; they still exist, but Windows 95 and Windows NT 4.0 integrate them as part of a more generalized layout. Any object that's part of the shell namespace can be viewed and accessed through Windows Explorer, which serves as the shell manager as well as the program you use to browse the file system. It's always active and working behind the scenes, though you can't see it.
      Windows Explorer handles objects by asking them to do everything needed: showing the content, enumerating the items, refreshing the window, responding to users clicking on menus and toolbars, drag and drop, and so on. Windows Explorer is responsible for the results of what other pieces of software do. Keep in mind that the Windows shell doesn't know (and never learns) how to handle your documents. It includes only a minimal set of common functionality. If you want to improve the shell support for a certain kind of document, then it's up to you to provide the module to do it. This new module must provide certain standard COM interfaces for the connection to take place.

Namespace Extensions
      You can customize the shell by adding new symbols, which is accomplished by creating special folders as Internet Explorer does in Figure 2. In the Temporary Internet Files folder, the list of the files downloaded from the net appears as if it were a regular directory. Instead, looking at the actual content of the Windows\Temporary Internet Files folder, you'll see four hidden directories whose files Windows Explorer interprets and presents in a customized manner.
Figure 2: Custom Folders
Figure 2: Custom Folders

      A new symbol may or may not be fully integrated into the Windows Explorer frame. If it is integrated, you can browse it as you would any other standard folder (like a directory). Such extensions are called nonrooted and display in Explorer's right-hand pane. Nonrooted means the extension is part of the existing hierarchy of objects. Conversely, rooted extensions require another instance of Windows Explorer to be started from a specific root path. Your view is limited to the subtree under the root path and you can't go up to a higher level. Furthermore, rooted extensions will always create a new window on the desktop. If the root is a file name (like the sample discussed here) the extension is nearly identical to an external viewer except it runs in the same process space as the Windows Explorer.
      For example, these lines will launch CABVIEW (an example of a namespace extension included with the Win32® SDK) on the mini.cab file:

 explorer.exe /root,
 {0CD7A5C0-9F37-11CE-AE65-08002B2E1262},
 c:\windows\system\mini.cab
      Whether you make your extension rooted or not depends upon what you want to do with it and the data you want to integrate with the shell. A wise (but not strict) rule to keep in mind is to create a nonrooted object if it represents a collection of something and would be an expandable node in a hierarchical structure. Otherwise, if you just want to offer a customized view of single objects (without running external apps), then the rooted approach might be better. In this case, the root path will be the file or directory itself.
Figure 3: Custom Nonrooted Extension
Figure 3: Custom Nonrooted Extension

       Figure 3 shows how a custom, nonrooted extension will appear. Note the modified toolbar, which sets up a new button with the Internet Explorer 4.0 icon. Now you can open up the MIND homepage more easily than ever!
      From the coder's perspective there are no differences between rooted and nonrooted extensions; they are mainly distinguished by the information you save in the registry. For a better understanding of the fundamentals of namespace extensions, I recommend that you read "Extending the Windows Explorer with Name Space Extensions," by David Campbell (MSJ, July 1996). You will learn, for example, that extending the way Windows Explorer works on a file requires a rooted extension because the current shell doesn't support browsing into file content. This feature is currently planned as part of Windows 98 (codenamed Memphis).

Shell Extensions
      A namespace extension is quite complex and requires a lot of coding work. You have to rely on poor documentation and very few samples. A shell extension is intrinsically simpler. It's a type of static local hook: local because it snoops around the user's activity within the Windows Explorer process; static because it is loaded and unloaded by the system and can only be disabled by unregistering it.
      There are things Windows Explorer knows to be customizable, such as a file's icon, context menus and property dialogs, copying a file, and drag and drop shortcuts. While performing any of these tasks, Windows Explorer checks for a registered shell extension that can perform user-defined processing. A shell extension is an inproc OLE server that implements a specific interface to Windows Explorer, based on what the extension actually does. A good source of information on this topic is Jeff Prosise's article, "Integrate Your Applications with the Windows 95 User Interface Using Shell Extensions," (MSJ, March 1995).

Writing a Namespace Extension
      Now I'll show you how to build a namespace extension and make the system recognize it. I'll be building a rooted extension that acts as a specialized viewer for DHTML documents. The junction point with the shell will be the file name itself.
      The first step will be constructing a minimal COM object with the required interfaces. The second item on the list is more ambitious and consists of designing a C++ class to walk through the HTML document object model that Internet Explorer 4.0 offers as part of DHTML. The idea is to set up an easy-to-use C++ interface that encapsulates most of the quirky details of COM programming. Having a class that allows a developer to write C++ code that looks like Visual Basic® or VBScript has been a dream of mine since the very first beta of Internet Explorer 4.0 arrived in my hands.
      The final step will be to put it all together and have a little fun with the registry. The result will be a nice tool (htmlview.dll) that opens up a window like the one in Figure 1 whenever you click on an HTML file within Windows Explorer. From the toolbar you can run the default browser you've installed on your system, although to work properly you'll need at least Internet Explorer 4.0 Platform Preview 2.
      Before starting with my first namespace extensions I tried to gather some information and samples. Unfortunately, I found only the aforementioned article by Dave Campbell and the CABVIEW sample that comes with Visual C++® 5.0. Some basic information is also available in the Visual C++ Books Online.
      The CABVIEW sample installs a module that allows you to view the contents of a CAB file as if it were just another directory on your disk. It displays a folder with the standard listview control and lists all the files included in the cabinet. The DHTML object model makes a simple HTML file quite similar to a container document such as a CAB. Thus, the CABVIEW sample would have been a good starting point for me. However, after a first look at the source code, I decided to write my own shell extension from scratch.
      Since a namespace extension is an inproc COM object, it must export a couple of global functions: DllGetClassObject and DllCanUnloadNow. The former is intended to return the pointer to the class factory for the new object. The file htmlmain.cpp contains a standard C++ implementation of these basic functions, along with the CLSID of the new object. A bare-bones namespace extension includes support for the IShellFolder, IPersistFolder, IEnumIDList, and IShellView interfaces and communicates with Windows Explorer through the IShellBrowser functions exposed by the shell. Figure 4 illustrates how these interfaces map into the classes of the sample project.

Figure 4: Classes and Interfaces
Figure 4: Classes and Interfaces

      The core of the extension is the CHtmlFolder class, which inherits from both IShellFolder and IPersistFolder. Figure 5 shows a complete list of methods you might want to implement. Not all of them are strictly necessary, except in a fairly complex extension. The checkmarks indicate which methods are usually worth it in a nontrivial implementation.
      The IPersistFolder interface is intended to provide a couple of entry points during the folder initialization. The Initialize function gets called when Windows Explorer is creating the folder object. It receives a PIDL (an identifier representing a relative path to the parent folder). This value, whose type is ITEMIDLIST, is guaranteed to be unique and meaningful only within the parent's domain. File names are a special case of PIDLs. To convert PIDLs into readable path names, call:

 SHGetPathFromIDList( pidl, m_szFile );
The namespace extension that is under construction here (htmlview.dll) is designed to affect single HTML file names. The line shown above, excerpted from IPersistFolder::Initialize, saves the document name into a CHtmlFolder custom data member I'll be using later.
      If you look closely at the source code for the CHtmlFolder class (see htmlfold.cpp), you'll notice that many IShellFolder methods aren't implemented and return the E_NOTIMPL error code. Don't let COM programming scare you: there is rarely the need to write a lot of code for all the methods exposed by an interface.
      IShellFolder provides Windows Explorer with all it needs to integrate your documents into its frame window and menus. It's responsible for the creation of more specialized objects that will take care of item enumeration, displaying content, setting and retrieving attributes, plus the other UI stuff I mentioned earlier.

View and Enumeration
      There are two functions in IShellFolder you need to know more about: EnumObjects and CreateViewObject. Both of them create subobjects. The former instantiates a CHtmlEnumItem class derived from the IEnumIDList interface and implements four methods: Reset, Clone, Next, and Skip. Defining these allows Windows Explorer to identify each component of the folder. A good implementation of this feature may be found in the CABVIEW sample. I chose not to support this in my HtmlView project, so all the CHtmlEnumItem methods are stubbed. If your own extension works on collections of items, you just have to specify how the next item can be found, how to jump over it, and how to rewind the enumerator or clone it. Figure 6 shows a table with a short description of IEnumIDList and IShellView interfaces.
      The most interesting part of writing namespace extensions comes when you finally decide how data should be displayed. This occurs inside IShellView::CreateViewWindow, whose source code is shown in Figure 7. A folder window is composed of a frame window with a status bar, a toolbar, and a view object. The implementation of this view object depends on your own app. In this case, it's a simple dialog with a treeview control that covers the entire client area.
      When Windows Explorer calls into CreateViewWindow, it passes the following arguments:

  • A pointer to the previous view, if any.
  • A pointer to a FOLDERSETTINGS structure with information about the current settings (large icons, small icons, autoarrange, and so on).
  • A pointer to the IShellBrowser interface to let the view change UI elements such as menus, toolbars, and status bars.
  • A pointer to the area destined for the view.
  • A buffer that will be filled with the actual handle of the view.
      The first two parameters are useless for my purposes, but can optimize performance or make the view more flexible in other circumstances. A container window implemented as a modeless dialog is not necessary, but can help when it's time to handle the messages a user generates by clicking on menus and toolbars. If you choose to implement one, make sure to assign your dialog the WS_CHILD style and no borders.
      When CreateViewWindow gets called, you can add buttons to the toolbar via the usual messages and structures. To send messages to the browser's UI objects you would use IShellBrowser::SendControlMsg, specifying which UI object you want:

 TBBUTTON tbb[3]; 
 m_pShellBrowser->SendControlMsg( FCW_TOOLBAR, TB_ADDBITMAP, 
                                  2, (LPARAM) &tbab, &lNewIndex );
 tbb[0] = lNewIndex;
 tbb[1] = lNewIndex+1;
As shown above, you have to specify the UI object, add your own bitmaps to the toolbar, and assign their progressive index to the custom buttons.
      The folder's menu is a resource shared between Windows Explorer and any namespace extension. You must create a new menu and pass it to the Windows Explorer via IShellBrowser::InsertMenuSB to let it populate as needed. Then you can add your own customization using the common API calls. Be sure to identify the submenus (File, Edit, View, Tools, Help) by ID instead of by position numbers (which might change), and assign your items IDs in the recommended range. If you fail to heed this advice, your code may work in the short run but not in future versions of the shell. Not surprisingly, the Windows Explorer installed by Active Desktop™ has a new Go menu right in the middle that will break noncompliant menu code.
      When you're finished modifying the menu, set it via IShellBrowser::SetMenuSB. Status messages to follow the same rules as in ordinary apps. You can show status text with SendControlMsg and the FCW_STATUSBAR flag, but don't forget to first turn your string into wide chars. The complete source code for the view object is in htmlview.cpp.
      After setting up the view, an instance of a new class is created to fill out the treeview control.

 m_pHtmlTree = new CHtmlTree( hwndTree,   
     m_pShellBrowser );
 m_pHtmlTree->Load( m_szFile );
It adds two main nodes: File Information and HTML Contents. The former subtree contains system information about the file like name, size, and creation date.

Working with the Registry
      Since a namespace extension is an inproc COM server, you need to register it under HKEY_CLASSES_ROOT\ CLSID and make sure it is visible under Windows NT. By clicking on an HTML file within Windows Explorer, you cause the execution of the following pseudocode:


 // assume szFile contains the name of the file you 
 // clicked
 pszCmd = DetermineDefaultCommand( szFile )
 ShellExecute( hwnd, pszCmd, szFile, NULL, 
               NULL, 0 );
ShellExecute is a Win32 API call that lets you spawn executables as well as document files. While launching an EXE requires just a call into CreateProcess, things get more complicated for documents. The pszCmd argument takes a string such as Open, Print, or Explore and represents the actions you can take over the document. Each verb refers to a certain path in the registry where a command string is stored. One of these verbs is assigned the default attribute. Windows Explorer retrieves this string and passes it up to ShellExecute.
      What happens with HTML files when you have Internet Explorer 4.0 installed? Suppose you click on myfile.htm. Usually, the default verb is Open, which displays in bold on the document's context menu. These settings are saved in the registry, as shown in Figure 8. When a verb is executed, the command line shown in the right pane will execute, using the actual file name. You can add custom commands too, and have them appear on the context menu.
Figure 8: HTML Registry Commands
Figure 8: HTML Registry Commands

      I added a new DHTML verb with a descriptive name of View Dynamic Content. It refers to the following line for execution:

 explorer /root,{CLSID},%1
{CLSID} is the actual CLSID of the extension. If you set DHTML as the default item, the pseudocode seen above will become the following:

 ShellExecute( hwnd, "DHTML", "myfile.htm", NULL, 
               NULL, 0 );
The actual system command, instead, will be :

 explorer /root,{CLSID},myfile.htm
      How is the connection between code and HTML files established? Open the Registry Editor under HKEY_CLASSES_ROOT and search for the ".htm" node. It should exist if you have any program registered to open HTML files. The entry's default value points to another registry path, under which you'll find a \Shell subtree with command strings. For Internet Explorer, this entry is named htmlfile (see Figure 8). This value might be different if you're using Netscape Navigator or some other browser as your default.

A C++ Class for Dynamic HTML
      Once the namespace extension is working, it's time to deal with DHTML. I want a C++ interface to walk seamlessly over the tags and objects that actually form an HTML document. Designing such a class is a topic worthy of an entire article in itself, so I'm going to write one that fits my specific purposes. The idea is to enumerate all the links, Java applets, scripts, ActiveX™ controls, and images in an HTML file.
      I'll use callback-based methods that accept a function address. This function will get passed pointers to a COM interface, be it IHTMLAnchorElement, IHTMLImageElement, or IHTMLElement. The header of the CDynHtml class is in Figure 9.
      The Init method creates an instance of the Internet Explorer 4.0 object model and saves a pointer to the document object interface IHTMLDocument2. The Internet Explorer 4.0 object model is in MSHTML.dll.


   hr = CoCreateInstance( CLSID_HTMLDocument, NULL, 
                          CLSCTX_INPROC_SERVER, 
                          IID_IHTMLDocument2, 
                          (LPVOID*) &m_pMSHTML );
The m_pMSHTML member is more intuitively expressed as the Document property of the WebBrowser control. The Load method puts the server to work on a specific HTML document. Once the file name is converted into wide characters, you call IPersistMoniker or IPersistFile to read it in. The proper interface to use is a distinction made on the first five bytes of the name. If it begins with "http:", use URL monikers. Otherwise, the simpler IPersistFile::Load would suffice (see Figure 10).
      At this point, I thought I was finished with my program. After loading successfully, the code attempted to access the Internet Explorer 4.0 object model to save pointers to the collections of links, tags, images and so on. Initially, it worked fine. Then things began to go bad. I did all my testing with a very simple HTML page, but as soon as I chose a more complicated one, everything crashed. After a bit of investigation, I figured out the cause of the problem, thanks to the WalkAll sample in the Internet Client SDK.
      It isn't safe for your software to access anything through the Internet Explorer 4.0 object model before it's ready. The Internet Explorer 4.0 document object raises a specific event when the initialization stage ends, so you can reliably make your calls. This translates into three more steps:
  • Implement the IPropertyNotifySink interface in the CDynHtml class.
  • Establish a connection point with the MSHTML server through the IPropertyNotifySink pointer.
  • Find a way to synchronize your module and the server, in such a way that it waits until MSHTML is ready.
Check out Figure 10 for excerpts of source code that does this.
      IPropertyNotifySink is intended to give your software (as a sink object) the opportunity to react to property value changes. Each time a property is about to modify its content, the object sends notifications by invoking OnRequestEdit and OnChanged. OnRequestEdit gives the sink a chance to either allow or reject the change. OnChanged arrives only after a new value has been set.
      The property to hook on is ReadyState. When OnChanged gets called, it is passed the DISPID of the involved property.

 if( dispID==DISPID_READYSTATE )
 {
   VARIANT vResult = {0};
   EXCEPINFO ei;
   UINT err;
   DISPPARAMS dp = {NULL, NULL, 0, 0};
 
   m_pMSHTML->Invoke( DISPID_READYSTATE, IID_NULL, LOCALE_SYSTEM_DEFAULT, 
                      DISPATCH_PROPERTYGET, &dp, &vResult, &ei, &err );
   m_readyState = (READYSTATE) V_I4(&vResult); 
 }
The code shows how to get the current value of a given property. V_I4 extracts the lVal internal field from a VARIANT variable and returns a 32-bit value. Next, compare the ready state against all the feasible constants and post an internal message to the current thread:

 switch( m_readyState ) 
   case READYSTATE_UNINITIALIZED:
   case READYSTATE_LOADING: 
   case READYSTATE_LOADED:
   case READYSTATE_INTERACTIVE:
      break;
   case READYSTATE_COMPLETE: 
      PostThreadMessage( GetCurrentThreadId(), WM_EX_DOCUMENTREADY, 0, 0 );
      break;
 }
      A sort of synchronization between the document server and your module is absolutely necessary. I adopted the same solution as in the WalkAll sample mentioned earlier. Using a Win32 sync object like an event should work as well.
      CDynHtml::Load has a message loop governed by GetMessage and DispatchMessage (see Figure 10). The module ends up here just after loading the file into memory and exits only when WM_EX_DOCUMENTREADY arrives. (The message is user-defined in DynHtml.h.) The window handle should be set to null. This effect is due to PostThreadMessage and confirms that the message you examine is exactly the one you were waiting for.
      At this point, accessing the Internet Explorer 4.0 object model should be absolutely safe.

Walking the Document Object Model
      The last thing I do in the Load function is save the pointers to the collections of interest in some internal data members. The Internet Explorer 4.0 document object model makes all tags in an HTML page available in a generic collection you can get with the get_all function. Furthermore, there are some collections that gather all images and links to other documents or sites. These are available through get_images and get_links. Needless to say, all the members are exposed by the IHTMLDocument2 interface, which is the Internet Explorer 4.0 document object root. get_links and get_images return pointers to IHTMLElementCollection, which gets you lists of items that you can walk and ask for more specific interfaces.
      Links and images, however, are not enough for the DHTML InfoViewer I have in mind. I also want to enumerate Java applets, ActiveX controls, and scripts. Since there are no built-in lists of those elements, I extracted them myself from the generic collection. This is done by GetAllTags, a protected member of the CDynHtml class. It accepts a ANSI string denoting the tag name you want it to search (say, APPLET, OBJECT, or SCRIPT) and returns a pointer to an IHTMLElementCollection interface. Here's the equivalent Visual Basic code:


 Dim pColl As IHTMLElementCollectionDim pElem As IHTMLElementSet pColl =  
     document.all.tags("OBJECT")Set pElem = pColl.item(i) 
      OK, so I've obtained the pColl reference. Now I need a way to walk it, item by item. When iterating on a single element I want to analyze its information and display it in a fashion that may vary quite a bit. One possible approach is to define some EnumXxx functions, nearly identical to the SDK's EnumWindows, EnumFonts, and so on. In CDynHtml I add EnumLinks, EnumObjects, EnumScripts, EnumApplets, and EnumImages. Each accepts a pointer to a specific callback function and a 32-bit buffer for custom data. Figure 9 shows the declarations.
      All the enumerators have a common skeleton. They scan the list represented by the IHTMLElementCollection pointer and pass the nth field down to the callback. Getting the nth item of a collection demonstrates all the complexity and the slowness of low-level COM programming, especially when compared to the same Visual Basic code. Such collections export the item function, which requires a couple of VARIANTs and an IDispatch pointer. While the first VARIANT takes the index of the item, the second seems to have no function.

 m_pScriptColl->item( varIndex, var2, &pDisp );
 IHTMLElement* pElem;
 pDisp->QueryInterface( IID_IHTMLElement, (LPVOID*) &pElem );
The pElem variable is what the callback functions will receive. This fragment represents what happens when executing the last line of the previous Visual Basic code.
      CDynHtml offers a few other benefits. For example, it has a GetTitle member that returns the title of the document as an ANSI string. GetSource reads the HTML source code, minus the header. If you want to know how many items form a given collection, just call GetNumOf specifying an ID for each supported list. This implementation of CDynHtml recognizes only links, applets, controls, scripts, and images.

Putting it All Together
      Up to this point, I've discussed namespace extensions and provided a C++ class to encapsulate most of the details you might face when you decide to take a walk in the Internet Explorer 4.0 document object model. Now it's time to put it all together and finish building that DHTML InfoViewer extension shown back in Figure 1.
      The file htmltree.cpp implements the CHtmlTree class, which I use to give a treeview-based rendering of the HTML content. The full source is available from the link at the top of this article. Its Load member function gets some file information and populates the File Information node as in Figure 11. The HTML Content node is also appended with subnodes for the various categories of HTML elements. Initially, Load adds only the total number of items, while HTREEITEM makes sure the specific nodes are stored in appropriate data members.

Figure 11: HTML Content Treeview
figure 11: HTML Content Treeview

      When the EnumXxx functions get called to complete the treeview, they receive a pointer to a given interface. Each callback can make a different use of it, according to the information it is expected to provide. MyLinkProc and MyImageProc, for example, simply call get_href to return the name of the object referenced, whether it's a mail address, Web site, or GIF file name. MyObjectProc extracts the CLASSID attribute of the ActiveX control and reads the description from the registry. MyAppletProc does the same with the Name attribute. As for scripts, I assume they always begin with <!-- and have the proc name enclosed in a couple of CR/LF characters. I also assume I'll find a LANGUAGE attribute. This is often true, though a more precise algorithm is welcome.

The New Explorer
      I developed this extension under Windows 95 without the Active Desktop of Internet Explorer 4.0 and the most recent improvements to the shell UI installed. Then I tested it under Windows NT 4.0, where I have a full installation of Internet Explorer 4.0. Well, it turns out that the new version of Windows Explorer has a different layout of windows and other features that require some adjustment to the code.
      The toolbar window is no longer a child of the frame window, but belongs to a coolbar, which in turn descends from a Worker window. Furthermore, when you create your view object, the toolbar is never empty but includes Back, Next, and Stop buttons (compare Figures 1 and 11).
      The first problem is that the toolbar is now expected to support text labels and show tooltips on its own, much like the MFC CToolBar class. Your software should detect the presence of the new Explorer and behave consequently.
      A possible way to do this is to search for a Worker class window, including a coolbar that contains a toolbar. If your namespace extension modifies the toolbar, then you can add strings to avoid a nasty visual effect: the buttons without text included display a random label (usually "Back," which comes first in the list). The same text is automatically used for tooltips, and you get no notification messages to take care of this.
      A second and more serious problem I noticed (possibly due to a ComCtl32 bug) seems to affect existing namespace extensions that modify the Windows Explorer toolbar. The buttons you add fail to notify the view they've been clicked, so there is no way for your software to expose methods through the Explorer bar. While waiting for a fix, you can subclass the toolbar and make it post WM_COMMAND messages to the view object. TB_GETHOTITEM, one of the new toolbar's messages, can help you to quickly identify which button the user clicked.

Summary
      If you like my DHTML InfoViewer and download it from this page, you'll get a zipped file with source code and a .reg file. Just replace the path where you saved the htmlview.dll and run htmlview.reg or import it to the Registry Editor. This will automatically enter all the necessary modifications to the registry. Clicking on any HTML file will display the InfoViewer window that lets you run Internet Explorer from its menu. If you don't want HtmlView to be the default command any longer, just uncheck File | Set As Default and you'll only be able to get to it from the context menu. This command will modify the default value of the entry


 HKEY_CLASSES_ROOT\
 Htmlfile\
 Shell\
and swap the default item for context menus from Open to View Dynamic Content (see Figure 12).
Figure 12: Custom Context Menu
figure 12: Custom Context Menu

      This article is based on Internet Explorer 4.0 Platform Preview 2. It is possible that some elements might have changed slightly upon the final release of Internet Explorer 4.0.


From the February 1998 issue of Microsoft Interactive Developer.