Transparent ATL Controls
Dr. Dobb's Journal
March, 1998 by Mark Nelson and Tom Armstrong |
Introduction
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.
Background checks
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.
Asynchronous Properties
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::Download()
method
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.
ReadyState
property values
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.
ReadyState
before rendering
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.
Details, Details.
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 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!
Project Files
- Project Source: trans.zip.
- The actual control: TransparentControl.dll.
- The necessary runtime ATL DLL: atl.dll.