|Dr. Dobb's Journal March, 1998
by Mark Nelson and Tom Armstrong
Implementing transparent ActiveX controls seems to be one of those programming tasks that is rapidly approaching FAQ status. The ability to display transparent GIF files in most popular Web browsers has apparently put quite a few developers into "me-too" mode. Unfortunately for programmers, Windows 95 and NT don’t provide much native support for the display of graphical images with transparent components. In this article, we provide a fairly simple set of steps that can be used to work around the shortcomings in the O/S. Using Microsoft’s Active Template Library (ATL) we develop an ActiveX control that will display a bitmap with a single transparent color. The control can be used with current and some past versions of Internet Explorer, Netscape Navigator (with the ScriptActive plug-in), Visual Basic, and most other ActiveX containers.
Transparency under Windows
When referring to bitmapped images, the term transparency has a straightforward meaning. A given image has an arbitrary number of transparent areas. When the image is drawn, the transparent areas don’t obscure the area behind the image in the Z-order. In the simplest case, we've all seen this used to good effect on the Web, producing images of complex objects that appear to be floating on top of a background.
Figure 1 shows a pair of transparent GIF files that float nicely above a background on a typical Web page.
Figure 1 - Screen capture of a pair of transparent GIF files used in a banner
Sites such as Mr. Showbiz typically have a standard background pattern that shows up on many or all of their pages. In Figure 1, the GIF on the left side has navigation bars that are used in an image map. A GIF file with the site logo is shown on the right. Both images feature transparent areas that let them blend in nicely with the background.
How do they do that?
After seeing this esthetically pleasing effect on the Web, we set ourselves to the task of duplicating it in an ActiveX control. For Windows programmers, encapsulation of a feature in an ActiveX control is definitely the method of choice.
Research turned up a couple of different ways to implement transparency in a control. The most straightforward of these methods requires the programmer to set up a windows HRGN object, used to define a non-rectangular drawing area. Figure 2 shows a simple example of a drawing region. As long as you can define your region as a path connecting a series of points (or elliptical regions), you can create a drawing region that Windows understands.
Figure 2 - A Windows Drawing Region
Once you have a drawing region defined, transparency is easy. In the simplest case, you can take advantage of an existing Windows 95 capability by defining a non-rectangular window. Once that's done, you simply draw the window as you normally would. The areas outside the region will be drawn by whatever resides behind the window.
Defining a drawing region works well with containers that implement the OCX 96 specification from Microsoft (shipping with the ActiveX SDK). In the best case, you can implement two pass drawing, which lets you draw the foreground part of your control first, then lets the container draw the background. This provides fast and flicker-free drawing of controls.
Ointment ready, enter fly
Defining drawing regions is great for some applications, but it might not be the best general purpose technique. We wanted to be able to draw any type of image with randomly configured transparent areas. This means having transparency controlled on a pixel by pixel basis. Attempting to define a region for any arbitrary bitmap is simply asking for a headache!
For our control, we instead used a masking technique that is described by Ron Gery in an MSDN article. This technique uses a simple masking technique that makes it trivial to use arbitrarily complex transparent regions.
The Gery algorithm assumes that you have a bitmap with a single color that defines the transparent area. Given that, the drawing portion of your code needs to execute the following steps:
Create a monochrome bitmap the same size as the bitmap you are going to draw.
Set all the transparent pixels in the monochrome bitmap to 1, and all the opaque pixels to 0.
XOR the screen region with the bits in your image bitmap.
AND the screen with your monochrome bitmap. This has the effect of leaving all the transparent areas unchanged, and setting the opaque areas to be black.
XOR the screen region with the image bits again. This sets all the transparent areas back to their original color, since the two XOR operations cancel one another. The opaque areas now contain the desired image bits, since XORing with solid black is the same as simply setting the bits.
This drawing algorithm is simple to implement using standard Windows raster operations. If you step through it and watch the effects when using the debugger, it will even start to make sense after a few passes.
This drawing scheme works pretty well with containers that adhere to the OCX 96 recommendations, but it makes one important assumption. The XOR/Mask sequence preserves the background behind transparent areas, but only if the background has already been drawn.
To programs like Internet Explorer 3.0, the ActiveX control is simply a child window that is responsible for drawing its entire rectangular area. So when IE3 is drawing the background for a Web Page, it will exclude the areas occupied by child windows, allowing them to draw their own background. So if I've set up a pleasing (or not!) background GIF, IE3 won't bother to draw it in any areas covered by my control, making any attempt at transparent drawing doomed from the start.
Microsoft has documented a way around this problem in Knowledge Base article Q165073. This article gives a code snippet that you can drop into a control's WM_ERASEBKGND handler. MFC code similar to that in the Knowledge Base article is shown in Figure 3.
In a nutshell, this code sends a WM_ERASEBKGND message to Internet Explorer, along with the device context for the ActiveX control. This convinces Internet Explorer to draw the background behind the control as if it didn't belong to a child window. This code doesn't cause any trouble for more sophisticated containers, such as IE4, because the WM_ERASEBKGND never gets sent to controls. Newer containers that implement the OCX '96 specification don't create child windows for each embedded control. Instead, each control renders itself directly on the container's device context.
Putting it together using ATL
To demonstrate the concepts discussed here, we created an ActiveX control called TransCtl that displays an eight bit BMP file, and treats all solid white areas ( RGB(255,255,255) ) as transparent. To keep things simple, the control doesn’t do any palette management, so if you are using it on 256 color displays you need to stick to the 20 system colors. (The system colors are generally always going to be present, regardless of the remaining 236 colors in the palette.)
We created this control using Visual C++ 5.0, and Microsoft's framework of the month, the Active Template Library. Programming with the ATL is somewhat more difficult than with MFC, but the resulting ActiveX controls are usually faster, smaller, and can eliminate dependencies on corpulent DLLs.
Working with the ATL is a Wizard-driven process, which means that programming isn’t just typing in code, it also involves quite a few menu selections and button depressions. The next section of this article will walk you through the process from start to finish.
Follow the bouncing ball
In the old days of command line compilers, we could have just included a single C file that held all the source code for this project. Things aren't so easy for authors using IDEs that feature Wizard-based development. The instructions for building this project with Visual C++ look more like a recipe in a cookbook, with an interesting mixture of dialog boxes, button presses, menu options and code snippets.
To build any ATL project using Visual C++, you select File|New|Project|ATL COM AppWizard. The dialog box asks you for a Project Name, which we set to TransparentControl, and a Location, which we filled in with C:\. We selected the default values on the next dialog, creating a DLL with no MFC support, and no merging of proxy/stub code. The Wizard provides the following dialog to confirm what you’ve just done:
Figure 4 - ATL COM AppWizard output
At this point we need to create the actual ActiveX control that will display our BMP files. To do this, we use Insert|New ATL Object, which brings up the ATL Object Wizard, shown in Figure 4. We select Controls|Full Control and click the Next button, which brings up a tabbed properties dialog. In the edit box asking for a short name, we entered TransCtl. In the Miscellaneous sheet of the properties dialog, we turned off the Opaque option.
The Stock Properties tab allows you to specify which stock properties your control will support. Our control needs the ReadyState stock property, which is completely supported by the ATL, but for some reason is absent from the Stock Properties tab. So, to include it in our control’s implementation we have to add the code ourselves, which we discuss later.
Figure 5 - The ATL Object Wizard
Clicking the OK button at this point generates all the files necessary to create the basic ActiveX control. You can build the control at this point, and insert it into Microsoft’s ActiveX Control Test Container (accessible from the Tools menu item.) But before it can do anything interesting, we need to add the code that assigns an image file to the control, loads the image file into memory, and draws it on the screen.
Dealing with the image file
Since the goal of the TransCtl is to display an image file, we clearly need a property that gives the name of the file. Adding properties to an ATL project is another Wizard driven process. In the ClassView tab of the Workspace window, you should have two classes defined: CTransCtl, and ITransCtl. Right click on ITransCtl item in the tree, and select Add Property. Choose a property type of BSTR, which is the canonical string type used in ActiveX controls. Choose a property name of ImageFile, and use the default get and put functions the Wizard suggests.
Figure 6 - Adding a property
While the Visual C++ Wizards help you quite a bit by adding the properties to the control, they don’t actually supply the internal implementation of the property. This needs to be done manually. First, we add a new member variable called m_bstrImageFile, of type CComBSTR to class CTransCtl in file TransCtl.h. Next, we initialize it with an empty string in the CTransCtl constructor. Finally, we implement the get_ImageFile() and put_ImageFile() functions in TransCtl.cpp.
The put_ImageFile() (shown in Listing 2) function has to do more than just copy a string into our member variable. It clears our internal bitmap that actually holds the image, starts up the download process, and informs the user that the control is in an incomplete state. We’ll cover the details of this whole process in the next few sections.
Until recently, a control's property values, such as the bits of an image, had to be stored directly within a file managed by its container. As the control is instantiated within the container, its property values are provided by the container via COM-based interfaces. This process of binding a control's property values is a synchronous operation. The container opens its storage file, locates the control’s persistent data, queries for the control's persistence interface (usually IPersistStreamInit or IPersistPropertyBag), and then calls IPersist*::Load, whereby the control sets its property values.
This technique works fine for most control properties such as background color, font, and so on because the data is small. However, large property values like the bits of an image, can be quite large, and in today's low-bandwidth environments, where 28.8 modems are considered fast, synchronously binding a control's properties just isn't tolerated. As an example, a Web page containing four ActiveX controls that each displayed a 100KB image would take several minutes before any aspect of the page would actually "display."
Back in early 1996, when Microsoft finally realized that the Web was much more than a passing fad, they developed a COM-based technology to solve the synchronous binding problem. This new specification provided a way for controls and containers to bind their properties through something called an Asynchronous Moniker. Microsoft also provided an implementation of the specification and deemed it a URL Moniker.
Monikers are used to name specific instances of COM classes. A moniker is itself a COM object that implements the IMoniker interface and whose purpose is to encapsulate the details of instantiating a particular object. The moniker hides, from the client, the process of locating, instantiating, and initializing a specific COM class. In other words, clients work through the standard IMoniker interface or the MkParseDisplayNameEx API and can ignore class-specific details.
Typically, monikers are used to bind to a specific instance of a COM object. However, they can also be used to bind to a remote storage or stream. Microsoft's URL moniker implementation allows a client application to asynchronously bind to a Web resource specified by a URL. The client application, by implementing the IBindStatusCallback interface, can treat the resource as a stream of bytes (via an IStream pointer). In other words, a client can download a remote file in an asynchronous manner by specifying only its URL, which is exactly what we need for our ImageFile property implementation.
The ImageFile property, then, becomes a string that holds just the URL for our bitmap file. The persistent data for our ImageFile property is no longer the bits of the image itself, but an embedded reference to them. To give you an idea of how this looks in a Web page, examine the ImageFile property in Figure 7.
Loading the image proceeds like this. As the control is instantiated by the container, it passes the image's URL as part of the synchronous property binding process. Once we have the URL, we initiate the asynchronous download process. The download occurs in a background thread, and when it is complete, we render the transparent control using our image data.
As developers we don't typically want to mess with all this detail, because the implementation is basically the same for any URL that we might access. This, of course, is why frameworks such as the ATL are so popular. ATL provides the CBindStatusCallback class for handling asynchronous downloads, and that's what we'll use to download our image data.
If you look closely at Listing 2, you'll notice that instead of using ATL's CBindStatusCallback class, we instead developed and uses a derived class named COurBindStatusCallback. The CBindStatusCallback implementation provided with version 2.1 of the ATL isn't quite ready for prime time. We had to tweak a few of its methods in order to get everything to work properly. You can examine the minor changes by downloading the code for our example.
The download process begins with a call to COurBindStatusCallback::Download. The Download() method is shown in Figure 8. The implementation is a little hard to understand at first, as is nearly every aspect of ATL, but with a look through ATL's source, we can figure it out.
COurBindStatusCallback is itself an actual COM object. Whenever we need to download a URL- based file, we just call the its static Download() method. As you can see from Figure 8, Download() creates an instance of itself and then starts the download process. The key point is that we pass in our control's this pointer and the address of our callback method. As the download proceeds, we will be notified through our OnData() callback method.
The implementation of the our callback method is shown in Listing 1. As data arrives, the OnData() method is called with a buffer containing the remote data and a flag indicating if this is the first, intermediate, or last data notification. We use the flag value to manage our download buffer. When the download is finished, we set the control's ReadyState property to complete. The rest of the code is general buffer management.
Are You Ready?
Before the addition of asynchronous properties, a control was ready for use as soon as it was instantiated and initialized by its container. Now, for those controls with asynchronous properties, their internal state may not allow immediate use after loading. Microsoft has added a new standard control property called ReadyState. A control that implements asynchronous properties uses this property to communicate its readiness to its users. Figure 9 shows the defined values for the ready state property.
Most of the states are self-explanatory. The difference between the INTERACTIVE and COMPLETE states is up to the control implementor. Those controls that provided interaction with the user (e.g., mouse clicks) should move to the interactive state as soon as possible, even if asynchronous downloading is still in progress.
For example, we might develop a button control that displays a bitmap. The control should move to the interactive state and allow its click event to fire even as the bitmap is downloading. Once the download is finished and the bitmap is rendered, the control moves into the complete state. A control developer should provide the user with interactive functionality as soon as possible.
For our transparent control, we don’t really provide any interactive behavior, and so we stay in the loaded state until our bitmap has finished downloading. By examining the OnDraw code in listing 2 and the excerpt in Figure 10, you can see that we defer rendering of the control until the bitmap download has completed.
There is also a new standard control event called ReadyStateChange that a control implementor can use to directly notify any users that the ReadyState property has change. For simplicity’s sake, though, we’ve left out this detail in our example.
Adding support for the ReadyState property to our control is easy. Just add a data member of type long with the name of m_nReadyState, and update the control’s IDL file with the get and put methods (See listing 5). Your control must also derive from CStockPropImpl instead of IDispatchImpl, and you need to add the property to your control’s property map. Discussion of ATL's implementation of stock properties is beyond the scope of this article. Beware, though, it involves large macros, blind unions, and unusual C++ syntax.
We've got DIBs
URL Monikers treat URL resources as a stream of bytes. It is up to the client application to add meaning to the returned stream. In our example, the stream contains a Windows DIB, or bitmap. A DIB object is different than a GIF file in that it doesn’t support progressive rendering, so our implementation requires the complete bitmap before we can draw the transparent image.
In most cases working with DIBs involves reading the bitmap structure in from a local file and working with a handle to the DIB. There’s even a nice API call (LoadImage) that makes this process easy. However, in our case, we have to manage a DIB structure in memory that is assembled asynchronously. Once the structure contains all of the necessary information, we need to realize the DIB within a Windows device context. To encapsulate this behavior we developed a DIB management class called CDib, which is based loosely on the WebImage example in the Win32 SDK. The header file is shown in Listing 3.
As data is downloaded it is stored in a buffer contained in our control's implementation class. Once the bitmap data is completely downloaded, the control's ReadyState transitions to complete. This allows the code from OnDraw() shown in Figure 11 to execute. The buffer information is passed to the DIB class, a device context is created based on the DC provided by the container, a DIB section is created, and finally the actual bits of the image are passed to the DIB section.
We are now ready to render our transparent image.
Displaying the CDib object
Once the image has been loaded into memory, all we have to worry about is displaying it. As we discussed earlier in this article, compatibility with IE 3.0 and other older containers means we need to add a handler for WM_ERASEBKGND, and use it to spoof the container into drawing the background for our control. Listing 1 shows the code that is added to the CTransCtl definition to process this message. First, we add an entry to the message map for the control, then we add the message handler itself. Essentially, all this handler does is pass the WM_ERASEBKGND message up to the parent window, along with a copy of the control's device context. (Note that the ATL Object Wizard doesn’t help you add message handlers, this is strictly a manual process.)
The actual drawing of the image is done in the OnDraw() member function of the CTransCtl class. If you've slogged through all this lengthy article wondering when we were actually going to show how to do transparent drawing, you have finally made it!
The OnDraw() function is called by the ATL framework, passing in a structure that contains various bits of information needed to actually paint information on the screen. Our implementation isn’t much more complicated than it would be if we were simply displaying the BMP file. We just have to add the code to create the monochrome bit mask, then perform the XOR/AND/XOR drawing method discussed earlier in this article.
The code shown in Figure 12 creates a monochrome bit mask in a compatible DC, relying on a Windows specific characteristic of monochrome bitmaps. When copying from a color bitmap to a monochrome bitmap, any color pixel that is identical to the color bitmap’s background color will get set to 1 in the monochrome bitmap. All other bits will get set to 0. With this knowledge in hand, you can see how the code shown below will handily create the bitmap needed by the masking algorithm.
Once the mask has been created, we can do the actual drawing using three device contexts. di.hdcDraw contains the screen device context, HDC(m_dib) is the device context containing the color bitmap for our image file, and hdcMask is the device context containing the monochrome bitmap. The three consecutive BitBlt() function calls in Figure 13 are all that is required to perform the transparent draw.
Developing controls with the ATL isn't quite as easy as with MFC, and we've left out some of the details of finalizing the implementation of our control. The details are covered briefly below and can be clarified by downloading and browsing the source.
Controls need to implement two additional interfaces to work effectively in the Web environment: IPersistPropertyBag and IObjectSafety. The IPersistPropertyBag interface allows a control and its container to work together to provide textual persistence. The older control persistence mechanism uses IPersistStreamInit, through which the container treats a controls properties as a stream of bytes. With IPersistPropertyBag, a control’s properties can be saved in an HTML-friendly format. Implementing IPersistPropertyBag is easy with ATL, just include IPersistPropertyBagImpl in your derivation.
A Web-enabled control implements IObjectSafety to inform its container that potential users cannot use it for malicious behavior. In other words, the control does not expose any functionality through which a user could harm the local system. Implementing IObjectSafety is only slightly more difficult, and this is because ATL's version of IObjectSafetyImpl doesn't work correctly. Instead of deriving directly from IObjectSafetyImpl, you derive from IObjectSafety and provide implementations for its two methods: GetInterfaceSafetyOptions() and SetInterfaceSafetyOptions().
Another important aspect of a Web-based implementations is that of control signing. An ActiveX control is really just a Windows DLL and so can easily corrupt (especially if the developer is trying to) a user's system. Microsoft's approach to dealing with this is possibility is to let you, the user, decide which ActiveX controls you choose to trust. Trust is handled by ensuring that each control is developed and distributed by a known entity. To be downloaded and installed on a user’s machine, a control must be digitally signed by such a known entity. To obtain a digital signature, just browse to www.verisign.com and agree to hand over your first born child (and $20.00). The rest is easy.
The final product
Since this is an ActiveX control, we can not only use it in our desktop applications, but in our Internet Applications as well. For a quick demo, we've set up an HTML page on Tom's web site that uses this control to display some transparent BMP files. You can view the control with either Internet Explorer or Netscape Navigator (with NCompass Lab's ScriptActive plug-in) by following this link to
www.widgetware.com, (mn: sorry, this site is no longer functional) and looking in the table of contents.
You can still use our control verbatim in your Windows applications, as long as your framework supports control containment. Fortunately, this includes the current releases of nearly all Windows development tools. But fear not, if you want to avoid the rigors of being an ActiveX user, you should still be able to take the old-fashioned approach to re-use by cutting and pasting our drawing code directly into your application. Sometimes the old ways are still best ways!
|The actual control||TransparentControl.dll|
|The necessary runtime ATL DLL||atl.dll|